Skip to content

Cubit v0.0.1 — walking skeleton: ingest → compare → record → dashboard#1

Merged
pedromvgomes merged 7 commits into
mainfrom
feature/skeleton
Jul 19, 2026
Merged

Cubit v0.0.1 — walking skeleton: ingest → compare → record → dashboard#1
pedromvgomes merged 7 commits into
mainfrom
feature/skeleton

Conversation

@pedromvgomes

@pedromvgomes pedromvgomes commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

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

  • Ingest (internal/criterion) — parse criterion's estimates.json
    (+benchmark.json) into a normalized, tool-agnostic model.
  • Compare + report (internal/model, internal/report) — baseline-vs-current
    diff with an advisory Markdown PR comment (adaptive ns/µs/ms, regression markers).
  • Persistence (internal/gitstore) — cubit-state orphan branch via git
    plumbing (no working-tree checkout), SHA-keyed, main-only history, compare-and-swap.
  • Dashboard (web/ + internal/dashboard) — Vite + React + TS trend charts
    with a CI variance band, built single-file and embedded in the binary;
    cubit serve hosts it locally or renders the standalone artifact.
  • CLI (cmd/cubit) — compare, record, serve, update (self-update,
    bulwark-mirrored), version.
  • Release / CI — goreleaser (linux+darwin × amd64+arm64, raw + archive,
    checksums), install.sh, composite action.yml (compare + sticky comment +
    artifact on PRs; record + push cubit-state on the default branch; never fails
    the build), plus CI (build/vet/test/gofmt) and a dashboard-drift guard.

Verification

  • go build / go vet / go test clean; gofmt clean.
  • gitstore integration tests, incl. a regression test for origin-based append.
  • Real-data loop verified end-to-end: cargo bench (DnsCache micro-benches, on
    the wardnet side) → criterion estimates.json → ingest → compare → cubit-state
    → dashboard render.
  • A code-review pass was run and its 6 findings fixed (self-consistent Append,
    non-blocking action semantics, robust store reads, batched dashboard reads).

Known follow-ups (post-0.0.1)

  • Macro resolution bench (in-process fake upstream) + AuthoritativeView/filter
    micro benches + iter_custom concurrency sweep.
  • More Go unit tests (ingest, report).
  • Bump to 0.1.0 once the first fully working + tested milestone lands.

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

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
@pedromvgomes
pedromvgomes merged commit 3c784e0 into main Jul 19, 2026
2 checks passed
@pedromvgomes
pedromvgomes deleted the feature/skeleton branch July 19, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant