Cubit v0.0.1 — walking skeleton: ingest → compare → record → dashboard#1
Merged
Conversation
Walking-skeleton spine for the performance-regression tool: - internal/model: normalized, tool-agnostic Run/Measurement schema + Compare() - internal/criterion: ingest target/criterion/**/new/estimates.json (+ benchmark.json) - internal/report: advisory Markdown PR comment (adaptive ns/µs/ms, regression markers) - cmd/cubit: compare, record, version, and bulwark-mirrored self-update (releases/latest redirect → semver → SHA-256 verify → atomic swap; 24h nudge, never in CI) - testdata: captured criterion sample + baseline record Verified end-to-end: build, vet, test clean; `cubit compare` renders the baseline-vs-current table against the sample tree. Deferred: gitstore (cubit-state orphan branch), serve + React dashboard, goreleaser/action/install.sh, wardnet benches, unit tests. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
Baseline persistence + read-back for the real CI loop: - internal/gitstore: append runs to an orphan branch (default cubit-state) via git plumbing (hash-object → temp-index update-index → write-tree → commit-tree → compare-and-swap update-ref), never touching the working tree. Records at runs/<sha>.json with an index.json manifest; main-only, SHA-keyed. Read/Latest fall back to origin/<branch> for the CI (fetched-not-checked-out) case. - record --persist: commit the run to the branch (CI default-branch step). - compare --baseline-ref: resolve the baseline from the branch by SHA, "latest", or "latest:<branch>", instead of a --baseline file. - integration test: append/read/latest, branch filtering, empty-commit rejection, and working-tree-untouched invariant. Verified: record --persist → compare --baseline-ref latest:main round-trips through the branch; working tree stays clean. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
…-mirrored) Make cubit installable and wireable into any repo's CI, mirroring bulwark's plumbing — minus the hard-fail step, since cubit is a light gate. - scripts/install.sh: curl-pipe installer (redirect-resolve latest, arch detect, SHA-256 verify, atomic install to ~/.local/bin). - .goreleaser.yml: single cubit binary, linux+darwin × amd64+arm64, raw + archive assets, checksums, install.sh as an extra release file. - .github/workflows/release.yml: tag-triggered build/test → goreleaser. - .github/workflows/ci.yml: build, vet, test -race, gofmt on PR/push. - action.yml: composite consumer action. auto-mode — compare + sticky PR comment + report artifact on pull_request; record + push cubit-state on a default-branch push. Fetches the state branch, bases record on origin's tip (CAS-safe), and never fails the build. Validated: gofmt clean, build/vet/test pass, all YAML parses, install.sh sh -n clean. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
The visualization side of the light gate: - web/: Vite + React + TS app rendering per-benchmark trend charts (X = commit, Y = median) with a confidence-interval variance band, hand-rolled SVG (no chart-lib dependency). Built single-file via vite-plugin-singlefile so the page is standalone — no external requests, works over file:// and CI CSP. - internal/dashboard: embeds the built index.html, assembles per-benchmark series from the cubit-state runs, and injects the data as a window global (Build/Render/Serve). Vendored build at internal/dashboard/assets/index.html. - cubit serve: host the dashboard locally on --addr, or render a standalone HTML file with --out (the CI artifact form). - action.yml: render dashboard.html in compare mode and upload it alongside the report. Verified: yarn build → single 199 KB html; go build/vet/test clean; serve returns HTTP 200 with data injected; render produces a self-contained page from real DnsCache benchmark runs. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
- assets/cubit-logo.png: the cubit rod icon (background removed, transparent). - report: PR comment header uses the logo via an absolute raw URL pinned to main (resolves in the consuming repo's PR, like bulwark), replacing the emoji. - dashboard: logo in the header, imported through Vite so vite-plugin-singlefile inlines it as a data URI — the page stays fully self-contained. - README: logo in the heading. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
gitstore: - Append: build the index AND read the manifest from a single consistent base (local branch, else origin/<branch>), and CAS the LOCAL ref. Previously tip() (local-only) and Index() (local-or-origin) could disagree: with only origin present, the tree was seeded empty while the manifest still listed every historical run, producing a divergent parentless orphan that dropped history and failed to push. Regression test added (TestAppendBuildsOnOriginTrackingRef). - Latest: fall back to the next-older matching run when an entry's blob is missing, instead of reporting "no baseline". - showFile/gitEnv: force LC_ALL=C so missing-path detection isn't defeated by localized git output. - Add History(): resolve the state ref once and read all runs, replacing the dashboard's O(N) Read-per-entry (each re-resolving the ref). dashboard: - Build uses gitstore.History (one ref resolution + one show per run). action.yml (light gate must never fail the build): - compare: on error, write a fallback comment and keep the job green (`if ! cubit compare ...`), instead of failing under `set -euo pipefail`. - record/push: best-effort — a failed record or rejected push logs to stderr and does not fail the workflow. Verified: gofmt/build/vet/test clean (incl. new regression test); real-data record → compare → dashboard loop unchanged. Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
Add a CI job that rebuilds the dashboard from web/ and fails if the vendored bundle the Go binary embeds (internal/dashboard/assets/index.html) differs. The build is byte-reproducible (deps pinned via yarn.lock), so a mismatch means the source was changed without re-vendoring. Pin the toolchain via packageManager: yarn@4.17.1 so CI uses the same yarn as local. Fix on drift: yarn --cwd web build && cp web/dist/index.html internal/dashboard/assets/index.html Claude-Session: https://claude.ai/code/session_016pTq6aVBJvTtXmwTgKw2Yh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Initial import of Cubit — a standalone performance-regression tracking tool
(Go CLI + React/TS dashboard) for the wardnet daemon's benchmarks. Sibling to
bulwark; it mirrors bulwark's
release/self-update conventions but is a non-blocking "light gate": the PR
comment and trend dashboard inform a human go/no-go, and it never fails the
consuming build.
What's included
internal/criterion) — parse criterion'sestimates.json(+
benchmark.json) into a normalized, tool-agnostic model.internal/model,internal/report) — baseline-vs-currentdiff with an advisory Markdown PR comment (adaptive ns/µs/ms, regression markers).
internal/gitstore) —cubit-stateorphan branch via gitplumbing (no working-tree checkout), SHA-keyed, main-only history, compare-and-swap.
web/+internal/dashboard) — Vite + React + TS trend chartswith a CI variance band, built single-file and embedded in the binary;
cubit servehosts it locally or renders the standalone artifact.cmd/cubit) —compare,record,serve,update(self-update,bulwark-mirrored),
version.checksums),
install.sh, compositeaction.yml(compare + sticky comment +artifact on PRs; record + push
cubit-stateon the default branch; never failsthe build), plus CI (build/vet/test/gofmt) and a dashboard-drift guard.
Verification
go build/go vet/go testclean;gofmtclean.gitstoreintegration tests, incl. a regression test for origin-based append.cargo bench(DnsCache micro-benches, onthe wardnet side) → criterion
estimates.json→ ingest → compare →cubit-state→ dashboard render.
Append,non-blocking action semantics, robust store reads, batched dashboard reads).
Known follow-ups (post-0.0.1)
AuthoritativeView/filtermicro benches +
iter_customconcurrency sweep.Merge Commit Message
feat: cubit walking skeleton — criterion ingest, cubit-state persistence, trend dashboard, release/action
Initial import of Cubit, a non-blocking performance-regression tool. Ingests criterion output, records SHA-keyed baselines on an orphan cubit-state branch, posts an advisory PR comment, and renders a self-contained trend dashboard. Ships goreleaser/self-update/install.sh and a composite action, all bulwark-mirrored minus the hard-fail