feat: load as a first-class citizen (DD-033) - #77
Conversation
Make the one dashboard push path mode-aware (StatusReporter gains mode + a load block; LoadRun feeds it live via a snapshotter so the dashboard updates during the soak), add a load UI card + mode to the campaign list, and polish the CLI status table (MODE column + mode-aware metrics from status.Load). Metrics are typed for a future OTLP export (latency=histogram, throughput/5xx =counter, drift/coverage=gauge, mode=attribute) so the exporter is a thin adapter — no OTel dependency added; the OTLP export is captured as its own roadmap DD tied to clustered-runners histogram merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
…Tel naming) - Live snapshotter: torn reads acceptable / terminal authoritative (no hot-path lock); live throughput uses the post-warmup window so it converges to terminal; one drift poller (snapshotter-owned, terminal reuses it). - OTel contract: basquin.load.latency -> basquin.load.request.duration; unit in the UCUM field not the name; the 1ms x 30000 bucket layout is the explicit histogram boundaries (breaking to change); campaign.id = resource attribute, mode = metric attribute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
5 TDD tasks: StatusReporter mode+load block; LoadRun live snapshotter (one drift poller, post-warmup window, torn-reads/terminal-authoritative); dashboard load card + list mode; mode-aware CLI status; e2e + docs + OTel roadmap. Metrics OTel-typed (request.duration/units/buckets/attributes) with the export deferred. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
…e drift poller (DD-033) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
…ove test into runner.coverage (DD-033) Reverts the public widening from the prior commit; instead moves LoadSnapshotTest into package runner.coverage (test/runner/coverage/LoadSnapshotTest.java), matching the repo's existing convention for tests that need a package-private helper (LoadRunTest, CostRankedReplayTest, CostCorpusPheromoneTest). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
… wrap (DD-033) Hoist the explore elapsed/rate #sub update back out of the mode else-branch so it fires immediately after the status fetch, not after the /clusters await + generation guard — matching pre-load-card behavior and no longer stalling behind a slow /api/analyze. Also fix a numeric-fallback nit (||'0' -> ||0) on the throughput card.
`basquin status` showed a fixed CAMPAIGN/TARGET/PHASE/COVERAGE/FINDINGS/ DASHBOARD table, so load campaigns rendered <none> coverage and 0 findings even though cp.Status.Load (throughput/latency percentiles/drift) is populated for them. Add a MODE column and a mode-aware METRICS column: explore shows coverage%/findings, load shows throughput rps and p99 latency from cp.Status.Load. Extracted campaignRow(cp) as a unit-testable helper so mode-aware rendering is covered directly (TestCampaignRowExplore/Load), independent of the fake-client-backed printStatus tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
Review fix on the mode-aware status table (Task 4): a load campaign still Running with no Status.Load published yet fell through to the explore-shaped "<cov> · 0 finds", misleadingly implying a fuzz search found nothing. campaignRow now special-cases mode=="load" && Load==nil to render "pending" (nil-guard ordering preserved, no dereference). Add TestCampaignRowLoadPending to isolate that path — the two prior tests (load-with-results, explore) don't exercise it, so a regression flipping the guard would otherwise pass silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
…bucket count, mark mode-aware-dashboard roadmap item delivered Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
…tus.load is authoritative Final-review precision: the JVM can exit before the daemon push loop ships an explicit terminal push, so the dashboard's last value is the last live snapshot (converged to terminal) and status.load is the authoritative terminal record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
|
@claude please review — load as a first-class citizen (DD-033): mode-aware dashboard (live during the soak, one push path) + CLI status. Explore is behaviorally unchanged; |
|
Claude finished @ianp94's task in 3m 40s —— View job Review: DD-033 — load as a first-class citizenTodo list
CI is green ( SummarySolid, well-documented change. The "make the one push path mode-aware" approach is the right call — no second pusher, additive JSON, and the DD-029 lock-free load-hot-path guarantee is genuinely preserved (only the snapshotter thread and the final terminal call touch One real finding, plus a couple of minor nits: 1.
|
…e load block on mode 1. resources/dashboard.html: wrap c.mode with esc() in tickFleet (every other reflected field is escaped; /ingest/status is schema-agnostic, so an untrusted mode value is one write-path from stored XSS in the fleet view). 2. StatusReporter.loadBlockJson: gate on "load".equals(mode) as well as loadRecorded — the block now appears iff mode==load (explicit invariant, no stale block on an explore snapshot), and StatusReporterLoadTest gets an @after mode reset (matching the repo's static-state test convention), removing the latent method-ordering dependency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXSdVrJQy6DvELDbtptG5p
|
Both findings addressed in
Nits #1–3 confirmed as intentional/non-issues, thanks for recording them. |
ianp94
left a comment
There was a problem hiding this comment.
Approving. Both Claude App findings verified as genuinely addressed in code, not just replied to:
- Fleet XSS:
tickFleetnow rendersesc(c.mode||'explore')— checked the actual diff hunk, consistent with every other reflected field. - Static-state ordering:
loadBlockJson()gates on"load".equals(mode) && loadRecordedwith the invariant documented in-code (a load block appears iff mode==load), plus the conventional@Afterreset — the stronger fix, as claimed.
Review highlights (practices + efficiency lenses):
- The snapshotter honors every design commitment: one drift poller (terminal reuses
lastDrift, single-poll fallback only if the snapshotter never ran), the live window anchored atmeasureFromNanosexactly like the terminal computation so live converges to terminal rather than diverging, torn-reads documented on the sharedcomputeLoadSnapshot(and shared code means the "live matches terminal" test is testing the real thing), daemon thread interrupted+joined cleanly, terminal push before mode revert. - OTel table commitments all recorded:
basquin.load.request.duration(not "latency"), UCUM unit field never in the name, the 1ms×30000+overflow bucket layout as a hard exporter contract,campaign.idas resource attribute vsmodeas metric attribute. - CLI: the
pendingstate for a load campaign with noStatus.Loadyet is exactly right — falling through to explore-shaped "0 finds" would have implied a fuzz search found nothing. Go tests cover both modes and the negative case (explore row must not show rps). - e2e closes the loop for real: queries the load campaign's own dashboard (token-authed) for
mode:load+ a non-empty load block before campaign GC. Green in CI. strFielddefaulting to "explore" keeps pre-DD-033 payloads rendering — good back-compat thinking in a schema-agnostic scrape.
One observation, no change requested: throughputRps is a JSON string ("%.1f") in both the load block and status.load. That's consistency with the existing DD-029 CRD contract (ThroughputRps is a string field), so changing it here would be a schema break for no gain — but the future OTLP exporter must read the registry doubles, never parse this JSON, which the DD record already mandates. Just flagging so it stays deliberate.
(reviewed at 2fb7f70)
What & why
Load campaigns were second-class everywhere they surfaced: the dashboard is hard-coded to explore (a load campaign showed
iterations=0/crashes=0/"no coverage source" — empty explore scaffolding), and the CLIstatustable showed<none>coverage /0findings for load. The real load numbers only reachedstatus.loadviakubectl.LOAD-MODE-DESIGN.mdpromised a dashboard push that was never built.DD-033 makes load first-class in the dashboard and the CLI.
How — one push path, made mode-aware
StatusReportergains amode+ a load block;snapshotJson()emits"mode":"explore"|"load"and, in load,"load":{throughputRps, latencyMs:{p50,p90,p99,max}, heapDriftKb, threadDrift, serverErrors, requests}. Explore JSON only gains a"mode"field — additive.LoadRunfeeds it live: a snapshotter thread computes throughput/percentiles from the live histogram + drift, so the existingDashboardClientloop carries it and the dashboard updates during the soak. No second pusher.status— aMODEcolumn + mode-aware metrics (rps · p99for load,cov% · findsfor explore,pendingfor a load campaign with no results yet).Correctness
#subelapsed/rate line stays independent of the/clustersfetch — a regression found and fixed in review; CLI explore row identical)./__basquin/driftpoller; the terminal drift reuses its last poll; the per-request worker loop gains zero synchronization (DD-029 lock-free load preserved).percentile()/histogram, and the final snapshot equals the terminal number. Live is approximate (torn reads, by design);status.loadis the authoritative terminal record (the dashboard shows the last live snapshot, converged).OTel-shaped, export deferred
Per the design discussion, metrics are typed for a future OTLP export (request latency = histogram
basquin.load.request.duration, unitms, theMAX_MS+2bucket boundaries as the contract; throughput/5xx = counters; drift/coverage = gauges;campaign.id= resource attribute,mode= metric attribute) so the exporter is a thin adapter. No OTel dependency added — the OTLP export is its own roadmap DD, tied to clustered-runners histogram merge.Verification
Unit:
StatusReporterLoadTest,LoadSnapshotTest(live math == terminal),CampaignRow{Explore,Load,LoadPending}. Full suite 124/124; Gocmd/basquin+ controller green. The in-cluster e2e asserts the load campaign's dashboard receivedmode=load+ a non-empty load block (CI runs it).Spec:
docs/superpowers/specs/2026-07-21-load-first-class-design.md· Plan:docs/superpowers/plans/2026-07-21-load-first-class.md🤖 Generated with Claude Code