Description
Migrate rfc6035-2otel task surface to just
Fleet-wide just migration per the frozen standard. This repo is Go + two Python generator programs
(Grafana dashboard/rule builders) + a Helm chart. It has exactly one Makefile, zero tracked shell
scripts, and no existing justfile.
1. Outcome
rfc6035-2otel has one top-level justfile, no Makefile. just check is the complete local PR
gate and is exactly what .github/workflows/ci.yml’s build-test job runs. just --list shows every
recipe grouped and documented. The two Grafana Python generators (grafana/build_dashboard.py,
grafana/build_rules.py) and the three deploy-time Python scripts under scripts/ stay as real
programs, reachable via just gen / just gen-check; the deploy scripts stay invoked directly by
grafana-sync.yml, untouched (see §10). AGENTS.md, CONTRIBUTING.md, README.md and
backlog/config.yml no longer tell anyone to run make.
2. The complete justfile
Drop this in as justfile at the repo root. Every value below (versions, flags, paths) is read from
this repo’s actual Makefile, .golangci.yml, .goreleaser.yaml, ci.yml and helm.yml — verify
golangci_lint_version, helm_docs_version and the govulncheck/goreleaser pins are still current
at the time this task is executed (check .github/workflows/ci.yml and helm.yml again first; they
may have moved since this task was filed).
set shell := ["bash", "-euo", "pipefail", "-c"]
version := `git describe --tags --always --dirty 2>/dev/null || echo dev`
commit := `git rev-parse HEAD 2>/dev/null || echo unknown`
build_date := `date -u +%Y-%m-%dT%H:%M:%SZ`
ldflags := "-s -w -X main.version=" + version + " -X main.commit=" + commit + " -X main.buildDate=" + build_date
tools_dir := justfile_directory() + "/.tools"
chart_dir := "charts/rfc6035-2otel"
golangci_lint_version := "v2.13.2"
helm_docs_version := "v1.14.2"
govulncheck_version := "v1.3.0"
goreleaser_version := "v2.16.0"
# show the task surface
default:
@just --list
# install repo-local tooling (idempotent, network allowed)
setup:
go mod download
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@{{golangci_lint_version}}
mkdir -p {{tools_dir}}
test -x {{tools_dir}}/helm-docs || GOBIN={{tools_dir}} go install github.com/norwoodj/helm-docs/cmd/helm-docs@{{helm_docs_version}}
# format Go sources in place
[group('dev')]
fmt:
gofmt -w $(find . -name '*.go' -not -path './vendor/*')
# verify Go formatting and justfile formatting; never mutates
[group('check')]
[no-exit-message]
fmt-check:
files="$(gofmt -l $(find . -name '*.go' -not -path './vendor/*'))"; test -z "$files" || { echo "Go files require formatting:"; echo "$files"; exit 1; }
just --fmt --check
# static analysis via golangci-lint (same config CI enforces)
[group('check')]
[no-exit-message]
lint:
golangci-lint run ./...
# go vet
[group('check')]
[no-exit-message]
vet:
go vet ./...
# run the Go test suite with the race detector; filter="Name" narrows via -run
[group('check')]
[no-exit-message]
test filter="":
if [ -n "{{filter}}" ]; then go test -race -run '{{filter}}' ./...; else go test -race ./...; fi
# verify go.mod/go.sum need no changes
[group('check')]
[no-exit-message]
tidy-check:
go mod tidy -diff
# apply go mod tidy
[group('dev')]
tidy:
go mod tidy
# regenerate the Grafana dashboard and alert-rule resources from the signal catalog
[group('gen')]
gen:
cd grafana && python3 build_dashboard.py
cd grafana && python3 build_rules.py
# verify generated Grafana resources match their builders, and run the generator unit tests (drift gate)
[group('check')]
[no-exit-message]
gen-check:
cd grafana && python3 build_dashboard.py --check
cd grafana && python3 build_rules.py --check
cd grafana && python3 -m unittest discover -s tests -t . -q
python3 -m unittest discover -s scripts/tests -v
# compile the binary into bin/
[group('build')]
build:
go build -trimpath -ldflags "{{ldflags}}" -o bin/rfc6035-2otel ./cmd/rfc6035-2otel
# THE GATE: everything a PR must pass, exactly what ci.yml's build-test job runs
[group('check')]
check: fmt-check lint vet test tidy-check gen-check build
# scan dependencies for known vulnerabilities
[group('check')]
[no-exit-message]
audit:
go run golang.org/x/vuln/cmd/govulncheck@{{govulncheck_version}} ./...
# run the RFC 6035 report parser fuzz smoke test (10s)
[group('check')]
[no-exit-message]
fuzz:
go test ./internal/vqreport -run='^$' -fuzz='^FuzzParse$' -fuzztime=10s
# build a snapshot release with goreleaser (no publish/sign/sbom/docker) — local parity with CI
[group('build')]
[no-exit-message]
build-snapshot:
go run github.com/goreleaser/goreleaser/v2@{{goreleaser_version}} release --snapshot --clean --skip=publish,sign,sbom,docker
# build a local docker image for smoke-testing (never pushes)
[group('build')]
image tag="rfc6035-2otel:dev":
docker build --build-arg VERSION={{version}} --build-arg COMMIT={{commit}} --build-arg BUILD_DATE={{build_date}} -t {{tag}} .
# CI-only superset of check: adds the vuln scan, fuzz smoke, snapshot release build and local image build
[group('check')]
ci: check audit fuzz build-snapshot image
# lint and render the Helm chart
[group('check')]
[no-exit-message]
helm-lint:
helm lint {{chart_dir}}
helm template rfc6035-2otel {{chart_dir}} > /dev/null
# regenerate the Helm chart README from its chart metadata
[group('gen')]
helm-docs: setup
{{tools_dir}}/helm-docs --chart-search-root charts
# verify the Helm chart README has no drift from its generated content
[group('check')]
[no-exit-message]
helm-docs-check: helm-docs
git diff --exit-code {{chart_dir}}/README.md
# remove reproducible build output and downloaded tools
[group('build')]
clean:
rm -rf bin {{tools_dir}}
3. Makefile disposition
git rm Makefile once every item below is verified working through just.
| Make target | Replacement | Notes |
|---|---|---|
build |
just build |
Same ldflags, same output path. BINARY/VERSION/COMMIT/BUILD_DATE become just variables computed once per invocation via backtick assignment. |
test |
just test |
Adds an optional filter="" param per the mandatory-vocabulary contract; go test -race ./... unchanged when no filter given. |
vet |
just vet |
Unchanged. |
fmt |
just fmt |
Unchanged (gofmt -w only — this repo’s Makefile never ran goimports on fmt, only in the golangci-lint formatters list; don’t add goimports to fmt/fmt-check, it isn’t currently enforced and adding it now is a scope change this task does not authorize). |
fmt-check |
just fmt-check |
Same gofmt -l check, plus just --fmt --check per §5.10 of the standard (not optional). |
tidy |
just tidy |
Unchanged. |
tidy-check |
just tidy-check |
Unchanged. |
dashboard |
just gen (partial) |
gen now runs both build_dashboard.py and build_rules.py together — there was never a reason to regenerate one without the other since build_rules.py imports build_dashboard (see grafana/build_rules.py:9). If a future need arises to regenerate just one, add a private helper recipe then — don’t split gen speculatively now. |
rules |
just gen (partial) |
Same as above. |
grafana-check |
just gen-check |
Identical body: both builders’ --check flags plus both unittest discover calls. |
helm-docs |
just helm-docs |
Same .tools/helm-docs install-once pattern, now via just setup as a recipe dependency instead of an inline guard. |
docker |
just image |
Renamed to the mandatory-vocabulary name image; gains a tag= default param (rfc6035-2otel:dev, matching the Makefile’s implicit $(BINARY):dev). |
check |
just check |
Now ALSO runs lint (golangci-lint run ./...) — the old make check deliberately excluded it (see AGENTS.md:15, quoted below) and CI ran it as a separate parallel job. Folding it into just check is required by the fleet standard’s completeness rule (§1: “If CI runs a check that check does not, the contract is broken”). This is the one intentional behavior change in this migration — call it out in the PR/commit description. govulncheck, the fuzz smoke, the goreleaser snapshot build and the docker image build stay OUT of check and live in the new ci superset recipe instead, because they are either network/registry-dependent, slow, or already run through a dedicated GitHub Action in CI (see §5) — pulling them into the fast local gate would slow down every just check run for marginal benefit. |
n/a (.PHONY line) |
delete | Meaningless in just; every recipe already always runs. |
Quoted for context, AGENTS.md:14-15 currently reads:
make check # fmt-check vet test tidy-check grafana-check build
golangci-lint run ./... # CI runs this and `make check` does NOT — a green make is not a green CI
This is exactly the gap just check closes. Update this section per §6 below.
After the justfile is proven locally and CI is switched (§8 order of work): git rm Makefile.
4. Script disposition
No tracked *.sh/*.bash/*.zsh/*.ps1 files exist in this repo (git ls-files | grep -E '\.(sh|bash|zsh|ps1)$' returns nothing). The only task-shaped scripts are Python, under grafana/
and scripts/.
| Script | Classification | Disposition |
|---|---|---|
grafana/build_dashboard.py (283 lines) |
KEEP | Real program: full dashboard-JSON builder with a panel registry, signal catalog parsing, and a --check drift mode. Wrapped by just gen / just gen-check. |
grafana/build_rules.py (226 lines) |
KEEP | Real program: alert/recording-rule builder, imports build_dashboard as a library. Same wrapping. |
grafana/common.py, grafana/panels.py, grafana/__init__.py |
KEEP | Library modules imported by the two builders above, not independently invoked — no recipe needed for these individually. |
grafana/tests/test_generated.py |
KEEP | Unit test suite for the generators (shell test-suite equivalent, §6 of the standard). Run via just gen-check, never absorbed. |
scripts/grafana-prune-rules.py (130 lines) |
KEEP | Real program invoked only by .github/workflows/grafana-sync.yml (a deploy workflow against live Grafana state, gated by OpenBao-minted credentials). Out of scope for this migration — see §10. Not reachable via any just recipe; it is not a developer/CI-check task, it is a production deploy step. |
scripts/grafana-verify-rules.py (187 lines) |
KEEP | Same as above — invoked only by grafana-sync.yml. |
scripts/verify-gitsync.py (153 lines) |
KEEP | Same as above — invoked only by grafana-sync.yml. |
scripts/tests/test_grafana_reconcile.py |
KEEP | Unit test suite covering the three deploy scripts above. Run via just gen-check (already was, under make grafana-check’s python3 -m unittest discover -s scripts/tests) even though the scripts it tests are themselves out of scope — the tests are cheap, fast, and were already part of the local/CI gate. |
No ABSORB candidates: there is nothing here that is a thin wrapper around 1-2 commands with no
control flow. Every script here is either a substantial generator program or a deploy-time tool with
real logic (subprocess calls to gcx, JSON diffing, retry-shaped verification). None gets deleted.
5. CI changes
.github/workflows/ci.yml
Add a setup-just step to every job that will call a just recipe (build-test, govulncheck,
fuzz). Do not add it to lint, goreleaser-snapshot, or docker-build — those three stay on
their existing GitHub Actions (golangci-lint-action, goreleaser-action, docker/build-push-action)
unchanged; converting an Action-based step into run: just … would lose its caching/annotation
behavior for no benefit, and the standard’s “never convert a uses: into run: just” rule extends to
these dedicated tool Actions, not just reusable workflow_calls.
- uses: extractions/setup-just@<pin-this-SHA> # v4
with:
just-version: '1.58.0'
Resolve <pin-this-SHA> to a real commit SHA of extractions/setup-just at execution time — every
other action in this repo’s workflows is SHA-pinned with a trailing # vN comment (see the
actions/checkout pin repeated throughout ci.yml/helm.yml); match that convention exactly, do not
leave a floating tag.
Per-job edits:
build-test: insertsetup-go(unchanged) then thesetup-juststep above, then change- run: make checkto- run: just check.lint: unchanged. Still usesgolangci-lint/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9withversion: v2.13.2. This pin must stay equal togolangci_lint_versionin the justfile — if one is bumped by Renovate/Dependabot without the other,just lint(local) and the CIlintjob silently diverge. Flag this coupling in a comment above the justfile’sgolangci_lint_versionline pointing atci.yml’slintjob.govulncheck: insertsetup-just, then replace bothrun:lines (go install golang.org/x/vuln/cmd/govulncheck@v1.3.0andgovulncheck ./...) with a single- run: just audit. Keepgovulncheck_versionin the justfile equal to thev1.3.0that was here — same coupling concern as above, note it the same way.fuzz: insertsetup-just, then replace- run: go test ./internal/vqreport -run='^$' -fuzz='^FuzzParse$' -fuzztime=10swith- run: just fuzz.goreleaser-snapshot: unchanged. Stays ongoreleaser/goreleaser-action@f06c13b6….docker-build: unchanged. Stays ondocker/setup-buildx-action+docker/build-push-action.ci-success: unchanged —needs: [build-test, lint, govulncheck, fuzz, goreleaser-snapshot, docker-build]stays exactly as-is; this is the branch-ruleset-gated check name, do not touch its job list or itsif: always()/ failure-detection logic.- Do not touch
permissions:,concurrency:, or anyactions/checkout/actions/setup-gopin.
.github/workflows/helm.yml
The lint-template job currently has no Go/just setup at all (it only uses azure/setup-helm). Add
extractions/setup-just there too (helm itself doesn’t need Go, but just does need installing).
Replace:
- run: helm lint "$CHART_DIR"
- run: helm template rfc6035-2otel "$CHART_DIR" > /dev/null
- run: |
make helm-docs
git diff --exit-code "$CHART_DIR/README.md"
with:
- run: just helm-lint
- run: just helm-docs-check
helm-docs-check needs Go on this runner (it calls just setup, which does go install helm-docs), so also add actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with
go-version-file: go.mod — copy the exact same step used in ci.yml. This is a genuine new
dependency this job didn’t have before; note it plainly in the commit message so it isn’t mistaken for
scope creep.
helm-success job (the needs: [lint-template] gate) is unchanged.
Workflows deliberately not touched
actionlint.yml, arm-automerge.yml, auto-rc.yml, codeql.yml, dependency-review.yml,
docker-security.yml, ghcr-cleanup.yml, publish.yml, release-assets.yml, release-please.yml,
scorecard.yml, trigger-docs-sync.yml, zizmor.yml, grafana-sync.yml — none of these has a
run: block containing build/test/lint/format/generate/validate logic that belongs in just. See
§10.
6. Docs and agent-contract changes
-
AGENTS.md:12-24(“The gate” section) — replace:## The gate ```bash make check # fmt-check vet test tidy-check grafana-check build golangci-lint run ./... # CI runs this and `make check` does NOT — a green make is not a green CICI additionally runs
govulncheck ./..., a 10s parser fuzz smoke, a GoReleaser snapshot build and a Docker image build.ci-successis the required check. Both gate commands are the tracker’sdefinition_of_done, so every task inherits them.Generated artefacts are never hand-edited:
dashboards/,alerts/andspec/signal-catalog.jsoncome from the builders undergrafana/. Edit the builder, thenmake dashboard/make rules.make grafana-checkfails if the committed output does not match.with:Task interface
This repo’s task surface is a
justfile. Discover it, don’t guess it:just --list # human-readable just --dump --dump-format json # machine-readable just --show <recipe> # what a recipe actually runsjust checkis the full local gate and is exactly what CI’sbuild-testjob enforces (fmt-check,lint,vet,test,tidy-check,gen-check,build). It must pass before you commit. It is the tracker’sdefinition_of_done, so every task inherits it.just ciis a superset CI also enforces via separate parallel jobs:audit(govulncheck),fuzz(10s parser fuzz smoke),build-snapshot(GoReleaser),image(Docker build). You don’t need to run these locally for every change, but they exist for parity when you do.- Prefer
just <recipe>over the underlying tool. If you are typinggolangci-lintorpytest, you wantjust lintorjust gen-check. - Run
justwith stdin from /dev/null. No recipe here is marked[confirm], but if one is added later, stop and ask before running it rather than passing--yes. - If a task you need does not exist, add a recipe with a
#doc comment and a[group(...)]rather than running a bare command.
Generated artefacts are never hand-edited:
dashboards/,alerts/andspec/signal-catalog.jsoncome from the builders undergrafana/. Edit the builder, thenjust gen.just gen-checkfails if the committed output does not match.Everything else in `AGENTS.md` (task tracking, PII rules, backlog CLI rules, git policy) is untouched — this migration only replaces the tool-invocation section. -
CLAUDE.md— no change. It only@-importsAGENTS.md. -
CONTRIBUTING.md:9-16— replace:Requires the Go version declared by `go.mod`. The local acceptance command is: ```sh make checkIt runs formatting, vet, race-enabled tests, module-tidiness verification, and a binary build.
make buildwritesbin/rfc6035-2otel;make testruns the race detector.with:Requires the Go version declared by
go.modandjust(https://just.systems). The local acceptance command is:just checkIt runs formatting, linting, vet, race-enabled tests, generated-artefact drift checks, and a binary build.
just buildwritesbin/rfc6035-2otel;just testruns the race detector (passfilter="TestName"to narrow it). Runjust --listfor the full task surface. -
README.md:12-15— replace:## Quick start ```sh make build ./bin/rfc6035-2otel -versionwith:Quick start
just build ./bin/rfc6035-2otel -version
7. backlog/config.yml
Current line:
definition_of_done: ["make check", "golangci-lint run ./...", "gh run list --branch main --limit 1 shows ci-success green at the exact pushed SHA"]
New line (this file is the one Backlog.md file edited by hand — see AGENTS.md’s note that
backlog/config.yml is the sole exception to “never hand-edit backlog markdown”; this is YAML, and
list-valued keys can’t be set through backlog config set):
definition_of_done: ["just check", "gh run list --branch main --limit 1 shows ci-success green at the exact pushed SHA"]
golangci-lint run ./... is dropped as a separate entry because just check now runs it
(just check → lint → golangci-lint run ./...) — listing it twice would be redundant and, worse,
would drift the moment just lint’s invocation changes without a matching edit here.
8. Order of work
- Add the
justfile(§2) at the repo root. Do not touch theMakefileyet — both exist side by side. - Run
just --fmt --check; if it fails, runjust --fmtonce and re-verify (--fmtwithout--checkmutates the file in place — expected here, this is the one time to run it unchecked). - Run
just checklocally end to end. Fix any drift between what the justfile assumes and what the toolchain actually does (tool versions especially — reread.github/workflows/ci.ymland.golangci.ymlfor the currentgolangci-lint/govulncheck/goreleaserpins before trusting the ones written into §2, they may have moved since this task was filed). - Run
just cilocally at least once (needs Docker running forjust image, andgithistory forjust build-snapshot’s version detection). - Diff
just check’s behavior againstmake check+ the CIlintjob run on the same commit — confirm they agree except for the deliberate addition oflintintocheck(§3). - Edit
.github/workflows/ci.ymland.github/workflows/helm.ymlper §5. Push to a branch (or direct tomainper this repo’s own git policy — it pushes straight tomain, seeAGENTS.md’s Git section) and watchci-successandhelm-successgo green callingjust, notmakeor raw tool invocations, for every job that changed. - Update
AGENTS.md,CONTRIBUTING.md,README.mdper §6. - Update
backlog/config.yml’sdefinition_of_doneper §7. - Grep the whole tree once more for any remaining
make/./scripts//Makefilereference this task missed (grep -rn 'make check\|make build\|make helm-docs\|make dashboard\|make rules\|make grafana-check' --include='*.md' --include='*.yml' ., excludingbacklog/history/archive files which are historical record and not part of the live contract). - Only once steps 1-9 are green and nothing references it:
git rm Makefile.
Steps 1-5 keep the repo green throughout (the Makefile and CI both still work). Step 6 is the only point where CI itself changes — verify it goes green before touching docs or deleting anything. The deletion in step 10 is strictly last.
9. Traps specific to this repo
grafana/build_rules.pyimportsgrafana/build_dashboard.pyas a Python module (import build_dashboardatgrafana/build_rules.py:9), which only resolves when the interpreter’s CWD isgrafana/. Thegen/gen-checkrecipes MUSTcd grafana &&before invoking either script — do not “simplify” this topython3 grafana/build_dashboard.pyrun from the repo root, it willModuleNotFoundErroron the rules builder.- Each recipe line is its own shell (§10 of the standard).
gen/gen-check’s four lines each independentlycd grafana(or don’t, for thescripts/testsline) — this is deliberate, not an oversight; don’t collapse them into one line with&&chains across thecdboundary, and don’t extract a sharedcd grafanaonto its own line expecting it to persist. helm-docsneeds Go on the runner to install itself (go install github.com/norwoodj/helm-docs/...) — the existinghelm.ymllint-templatejob never had Go before (it only runsazure/setup-helm). Addingjust helm-docs-checkthere is a genuine new dependency (anactions/setup-gostep), not a no-op wiring change. Don’t skip it silently.- Two independent CI gates, not one.
ci-success(fromci.yml) andhelm-success(fromhelm.yml) are separate required checks with separate path triggers.just checkmatchesci-success’sbuild-testjob; it intentionally does NOT includehelm-lint/helm-docs-check— those only need to run whencharts/**changes, which is exactly whathelm.yml’s path filter already does. Don’t fold Helm recipes into the maincheck/cichain. golangci_lint_versionandgovulncheck_versionin the justfile duplicate pins that also live in.github/workflows/ci.yml(thelintjob’sversion: v2.13.2input, and the removedgo install golang.org/x/vuln/cmd/govulncheck@v1.3.0line folded intojust audit). These are two independent places Renovate/Dependabot can bump one without the other. There is nojustmechanism to share a version constant with a YAML workflow file across process boundaries in this fleet’s no-remote-import model (§7 of the standard) — accept the duplication, but leave the coupling comments from §5 in place so a future bump PR touches both.go run pkg@version(used forauditandbuild-snapshot) hits the network and Go module proxy on every invocation unless the module is already in the local module cache — this is a deliberate tradeoff to avoid a stateful install step insetup; if this becomes a problem (offline dev, flaky proxy), the fallback isgo install pkg@versioninto.toolsinsidesetup, mirroring thehelm-docspattern — do not silently switch to that without updating bothsetupand the two recipes’ bodies together.just --fmtis destructive (rewrites the justfile’s whitespace/formatting) — only run it bare once during initial authoring (§8 step 2);fmt-check’sjust --fmt --checkis the one that belongs incheckand never mutates.- The
testrecipe’sfilterparam uses single-quote interpolation (-run '{{filter}}') per §10’s quoting trap — do not remove the quotes, an unquoted{{filter}}containing a space would split into two arguments.
10. Out of scope — do not touch
scripts/grafana-prune-rules.py,scripts/grafana-verify-rules.py,scripts/verify-gitsync.py— deploy-time tools invoked only by.github/workflows/grafana-sync.ymlagainst live Grafana state behind OpenBao-broker-minted, per-run credentials. Not a developer/CI-check task surface; §6 of the standard explicitly excludes “scripts invoked by something other than a developer or CI” (this is invoked by a push-triggered deploy workflow acting as an operator, not a PR gate) — leave these scripts, and every step ofgrafana-sync.yml, exactly as they are..github/workflows/grafana-sync.ymlin full — do not addsetup-just, do not touch itspython3 scripts/...invocations, itsgcxinstall/context steps, or its GitSync push/verify logic..github/workflows/publish.yml— calls the reusableuses: rknightion/.github/.github/workflows/container-publish.yml@…workflow. Never convert auses:reusable-workflow call intorun: just(§8 of the standard, and §13’s anti-pattern list)..github/workflows/release-please.yml,auto-rc.yml,release-assets.yml— release-please and its satellite workflows are GitHub-native and out of scope by name (§8 of the standard). Do not fold release logic intojust, and do not touch their auth (per this account’s standing rule, they mint per-run tokens from the OpenBao broker — never provision a durableRELEASE_PLEASE_TOKEN)..github/workflows/codeql.yml,zizmor.yml,actionlint.yml,scorecard.yml,dependency-review.yml,docker-security.yml,ghcr-cleanup.yml,arm-automerge.yml,trigger-docs-sync.yml— all GitHub-native or fleet-standard security/automation workflows, none with build/test/lint logic in arun:block. Leave untouched.- The
lintjob’sgolangci-lint-action, thegoreleaser-snapshotjob’sgoreleaser-action, and thedocker-buildjob’sdocker/build-push-actionsteps inci.yml— dedicated Actions with caching/annotation behavior a rawrun: just …would lose. Leave theuses:steps as they are; only therun:steps in those jobs (there are none left to touch besides what §5 already lists) change. archive/,codex/,docs/superpowers/— historical/scratch material, not part of the live task surface, not referenced by any recipe.spec/protocol.md,spec/signal-catalog.json,dashboards/rfc6035-2otel.json,alerts/grafana-managed/**— generated/spec artifacts, already covered byjust gen/just gen-check; do not hand-edit them as part of this migration.backlog/markdown files — never hand-edited; drive all task/doc changes through thebacklogCLI.backlog/config.ymlis the sole named exception (§7).
Acceptance Criteria
- #1 A top-level justfile exists at the repo root defining all seven mandatory recipes (default, setup, fmt, fmt-check, lint, test, check) plus gen, gen-check, vuln, fuzz, snapshot, image, ci, helm-lint, helm-docs, helm-docs-check, clean
- #2 just check runs fmt-check, lint, vet, test, tidy-check, gen-check, build, vuln, and fuzz, and .github/workflows/ci.yml build-test runs exactly just check
- #3 just –fmt –check passes with no diff
- #4 just –list shows a doc comment for every public recipe and a [group(…)] for every public recipe except the ungrouped default and setup recipes
- #5 Makefile is deleted (git rm), with no remaining reference to make in AGENTS.md, CONTRIBUTING.md, README.md, docs, or any workflow
- #6 grafana/build_dashboard.py, grafana/build_rules.py, scripts/grafana-prune-rules.py, scripts/grafana-verify-rules.py, and scripts/verify-gitsync.py all still exist as files; the first two are reachable via just gen/just gen-check, the last three remain invoked only by grafana-sync.yml and by no just recipe
- #7 .github/workflows/ci.yml build-test, govulncheck, and fuzz jobs call just check / just vuln / just fuzz via setup-just; lint, goreleaser-snapshot, and docker-build retain their action-backed implementation; ci-success retains its existing needs list
- #8 .github/workflows/helm.yml lint-template calls just helm-lint and just helm-docs-check via added setup-go and setup-just steps, while helm-success still gates on lint-template and preserves helm-schema-validate
- #9 AGENTS.md gate section is replaced with the Task interface contract naming just check as the definition_of_done, and CONTRIBUTING.md/README.md no longer mention make
- #10 backlog/config.yml definition_of_done lists just check instead of make check and golangci-lint run ./…
Definition of Done
- #1 gh run list –branch main –limit 1 shows ci-success green at the exact pushed SHA
- #2 just check
Implementation Notes
Ratified task comments supersede the original audit/build-snapshot names: the implemented public recipes are vuln and snapshot. Added renovate annotations plus a justfile regex manager, and raised GoReleaser from v2.16.0 to v2.18.0 after v2.16.0 failed to compile under Go 1.27; v2.18.0 completed the snapshot locally.
Validation passed: just –fmt –check; just –dump –dump-format json; isolated just check; just ci (including GoReleaser snapshot and Docker image); just helm-lint; just helm-docs-check; actionlint; and Renovate config validation. A completed CodeRabbit review produced five findings, all fixed and revalidated; the follow-up review was rate-limited. CI run 33258154498 completed successfully at f8b435e773b296fa87eefd735d1a9fe5de557f3b, with ci-success green and Helm green.
Comments
author: campaign-ordering created: 2026-08-29 09:18
Fleet ordering — WAVE 2. Starts after the Wave 0 pilot (sf2loki / SFL-0073) and the Wave 1 hubs land.
Within Wave 2 the order is free — these repos do not depend on each other. Batching by language is worthwhile so one lane reuses its Makefile-to-recipe mapping across similar repos.
Do not start before the pilot reports. The standard may be amended off the back of it, and picking this up early risks coding against a superseded seam.
Provisioning just in CI. Which mechanism depends on the runner, and the two must not be mixed:
| Runner | Mechanism |
|---|---|
arc-arm64 (m7kni self-hosted) |
just is baked into the runner image by m7kni/ci-tools (runner-image/Dockerfile, ARG JUST_VERSION). Do not add extractions/setup-just, and delete the step if this repo already has one — it installs a second just earlier on PATH and turns the image pin into a lie. |
GitHub-hosted (all rknightion repos) |
extractions/setup-just, SHA-pinned, with an explicit just-version:. |
Both sides currently sit on 1.58.0 and are Renovate-managed. ci-tools’ Tool version drift workflow fails if the Dockerfile ARG and the published image ever disagree, and lists any repo still carrying a second pin.
While you are in the workflow files, check the hub pin. On 2026-08-29 Renovate was unfrozen for rknightion/.github in m7kni/renovate-config — it had been enabled: false on the mistaken belief that callers tracked @main, which froze the fleet across 19 different hub SHAs (v1.3.1 June → v1.9.7 August) so that no hub fix ever propagated. Bumps now arrive as one grouped, CI-gated, automerged PR per repo. A uses: whose comment is not a real # vX.Y.Z still cannot be bumped (it resolves to a digest-only update, which the fleet rules disable) — if you find one, repair the comment as part of this task.
author: campaign-ordering created: 2026-08-29 10:43
Standard amendment — ci is the sanctioned superset of check (RATIFIED)
This supersedes the frozen wording “check is the complete local gate and reproduces every CI job that can run off a GitHub runner”, which several lanes could not honour without making the pre-commit gate depend on a Docker daemon.
The definitions now are:
check— everything that runs with only the language toolchain installed. This is the pre-commit gate. A leg that runs on a bare toolchain belongs here however long it takes.ci—checkplus the legs CI gates that need a Docker daemon, a service container, or cross-compilation, and nothing else. Written asci: check <heavy legs>.
Every leg you put in ci must carry a comment naming which of those three it needs. That comment is the guard: without it ci becomes the bin for anything slow or awkward, check quietly stops meaning much, and the fleet is back to a per-repo gate.
Eleven of the 42 lanes arrived at this shape independently before it was ratified, which is why it won.
If this repo has no such legs, it has no ci recipe at all and check is the whole gate. Do not add an empty one.
author: campaign-ordering created: 2026-08-29 10:57
Fleet alignment — the 2otel family converges on one CI shape
These seven Go repos are near-identical applications and had drifted into two naming dialects and materially different coverage. The migration rewrites every run: block anyway, so converge them in the same change rather than preserving the drift in new clothes.
Canonical job names — used by tailscale2otel, graph2otel, polylens2otel and rfc6035-2otel, so this is the majority convention, not an invention:
build-test · lint · govulncheck · goreleaser-snapshot · docker-build · coverage · ci-success
opnsense2otel and transceiver-exporter currently use a second dialect — tests, race, docker-build-verify. Rename to the canonical set as part of this task.
ci-success is the only check the branch ruleset gates, so jobs can be renamed or merged freely provided ci-success’s needs: list is updated in the same commit. Never rename ci-success itself.
Required gates, and where each lives after the migration:
| Gate | Recipe | Note |
|---|---|---|
build + test + -race |
just test |
-race belongs in the standard test run |
| golangci-lint | just lint |
needs a .golangci.yml, schema v2 |
| gosec | just lint |
a golangci-lint linter, NOT a separate job — enable it in .golangci.yml. Four of the seven already do it this way; a standalone gosec job would be a third dialect |
| govulncheck | just vuln |
pinned golang.org/x/vuln/cmd/govulncheck@v1.3.0, matching the family |
| goreleaser snapshot | just snapshot |
cross-compile ⇒ belongs in ci, not check |
| container build | just image |
needs a Docker daemon ⇒ belongs in ci, not check |
Already done for you (2026-08-29): govulncheck was added to opnsense2otel, transceiver-exporter and codexlb2otel ahead of the migration, because those three had no dependency vulnerability scanning at all. Convert those jobs to just vuln like any other; do not re-add them.
Still missing, fix as part of this task:
opnsense2otel— has.golangci.ymlbutgosecis not enabled in it.transceiver-exporter— no.golangci.ymlat all, and no-racein its test job.codexlb2otel— no.golangci.yml, no-race, no container build, and noci-successjob and no branch ruleset, so nothing gates its CI. Adding an aggregator is the right fix but is a separate decision; raise it rather than assuming.
One known trap: the govulncheck@v1.3.0 pins are invisible to Renovate — go install pkg@version inside a run: block matches no manager. All five are four minor versions behind (current is v1.7.0). Once the version moves into the justfile as a # renovate:-annotated := assignment, it becomes managed. That is a real benefit of this migration, not incidental.
author: campaign-ordering created: 2026-08-29 11:20
Correction — moving a pin into the justfile does NOT make it Renovate-managed
The 2otel alignment comment above ends with a claim that needs narrowing. It says the govulncheck@v1.3.0 pins become managed “once the version moves into the justfile as a # renovate:-annotated := assignment”. The conditional in that sentence is doing real work, and the first completed migration did not satisfy it.
Verified on tailscale2otel at origin/main after TSO-0025 closed:
justfile:21—govulncheck_version := "v1.3.0", with no# renovate:annotation above it.renovate.json— no justfile matcher at all: nocustomManagersentry, nothing pointing at/^justfile$/.justfile:17— a comment stating the pin tracks thego installline in the CI jobs, so the workflow is still the source of truth.
The version relocated and nothing else changed. It is exactly as invisible to Renovate as it was in the run: block, and still four minors behind (v1.3.0 against v1.7.0).
Two things are required, and neither is implied by “move the pin into the justfile”:
- The
:=assignment carries a# renovate: datasource=… depName=…annotation directly above it. renovate.jsonpoints a custom manager at the justfile — thecustomManagers:dockerfileVersionspreset does not cover justfiles; that one only matches Dockerfiles and Containerfiles.
Treat “the pin is now managed” as false unless you have done both and checked. Do not record it as a benefit of this migration in a final summary without verifying renovate.json yourself.
Credit: caught by the tailscale2otel lane on its closeout, against the claim as originally written here.
Final Summary
Replaced the Makefile task surface with a formatted, grouped justfile; routed CI and Helm task commands through pinned Just setup; preserved reusable workflows and deploy scripts; and aligned local/CI GoReleaser at v2.18.0. Verified by the local gates, workflow/config validators, and exact-SHA green CI run 33258154498.