# Platform battery as one command: `make check` is what CI calls and what a session runs before # handing the tree over. Toolchain and linter are PINNED (never "latest"): a gate that changes # under you on someone else's machine is not a gate. Shape mirrors backend/Makefile deliberately. GO ?= go # go.mod's floor is 1.26.4 (the engine's), but the BUILD toolchain floor here is HIGHER, and for one # reason: this module is the one exposed to the network, so it takes the standard library's security # fixes as soon as they are released. # # ⚠ Raised 1.26.5 → 1.26.6 on 13.08 by `make vuln`, not by choice: the advisory database published # five standard-library vulnerabilities against 1.26.5 — net/http, crypto/tls, net/url, encoding/xml, # encoding/asn1 (GO-2026-6218/6090/6089/6088/5972) — all fixed in 1.26.6, and govulncheck traces two # of them into paths this service calls. The same battery is green on 1.26.6 and the scan is clean. # HOW the floor is actually enforced, because the first version of this raise enforced nothing: the # check below COMPARES versions (its predecessor was a regex that matched the very 1.26.5 it was # written to refuse), and go.mod carries `toolchain go1.26.6`, which every build reads whether or not # it calls make — with GOTOOLCHAIN=auto such a host fetches this toolchain, with =local it stops. # The comparison itself is pinned in internal/gates. GO_MIN_VERSION := 1.26.6 GOLANGCI_LINT ?= golangci-lint GOLANGCI_VERSION := 2.12.2 # sqlc generates the query layer of the glue-free block of internal/pgstore. Pinned EXACTLY, like the # linter above and for a sharper reason: the generator's output is committed to the tree, so a # different version silently produces a different diff and `sqlc diff` then fails on a clean checkout. # Release 22.04.2026, checked live against the vendor rather than remembered. SQLC ?= sqlc SQLC_VERSION := 1.31.1 .PHONY: build vet fmt lint test check tools-check version-check vuln fuzz sqlc-check sqlc-generate build: tools-check $(GO) build ./... vet: $(GO) vet ./... # `gofmt -l` exits 0 even when it names files, so the emptiness of its output is the assertion. fmt: @test -z "$$(gofmt -l .)" || { echo "gofmt: not formatted:"; gofmt -l .; exit 1; } # GO_VERSION is what the gate JUDGES, and it is a variable so the judgement can be tested with # versions this host does not have: `make version-check GO_VERSION=go1.26.5` must fail. GO_VERSION ?= $$($(GO) env GOVERSION) # version-check COMPARES rather than matches. The predecessor of this rule was a regex over # `go version`, and it accepted the very release it was written to refuse — 1.26.5 matched # `go1\.26\.([5-9]|…)` — while GO_MIN_VERSION lived only in the failure message. A comparison has no # such gap: `sort -V` orders 1.26.10 above 1.26.9, which a regex over digits gets wrong too, and it # is the same tool the release engineering of this repo already relies on. version-check: @have="$$(printf '%s' "$(GO_VERSION)" | sed 's/^go//')"; \ case "$$have" in *[!0-9.]*) \ echo "Go $(GO_MIN_VERSION)+ required, and a release: a prerelease or development toolchain does not carry the fixes its number promises; got: go$$have"; \ exit 1;; esac; \ lowest="$$(printf '%s\n%s\n' "$$have" "$(GO_MIN_VERSION)" | sort -V | head -1)"; \ test "$$lowest" = "$(GO_MIN_VERSION)" || { \ echo "Go $(GO_MIN_VERSION)+ required (standard-library security fixes in a network-facing module); got: go$$have"; \ exit 1; } tools-check: version-check @$(GOLANGCI_LINT) --version 2>/dev/null | grep -q " $(GOLANGCI_VERSION) " || { \ echo "golangci-lint $(GOLANGCI_VERSION) required (findings are version-dependent)."; \ echo "install: https://github.com/golangci/golangci-lint/releases/tag/v$(GOLANGCI_VERSION)"; exit 1; } @test "$$($(SQLC) version 2>/dev/null)" = "v$(SQLC_VERSION)" || { \ echo "sqlc v$(SQLC_VERSION) required (the generated query layer is committed, so the version decides the diff); got: $$($(SQLC) version 2>/dev/null || echo none)"; \ echo "install: go install github.com/sqlc-dev/sqlc/cmd/sqlc@v$(SQLC_VERSION)"; exit 1; } # sqlc-check is the AUTHORITATIVE freshness gate: it re-runs the generator and fails if what is in the # tree is not what it produces. It is a prerequisite of `check` rather than a target somebody has to # remember, because a target `make check` does not call is not a gate. # # The battery carries a SECOND, weaker check of the same property that needs no tool # (pgstore.TestEveryGeneratedQueryMatchesItsSourceFile): this one proves the whole generated file, # that one proves the SQL text, and only the second survives on a host without sqlc. sqlc-check: tools-check $(SQLC) diff sqlc-generate: tools-check $(SQLC) generate lint: tools-check $(GOLANGCI_LINT) run --timeout=10m ./... # -race needs cgo. If the C toolchain is missing this fails loudly rather than quietly proving less. test: $(GO) test ./... -race -count=1 # The battery. One verbose run under -race serves both purposes (PD-17: it used to run the suite a # second time without -race just to harvest skip names), and it NAMES the tests that did not run — # the database-backed ones skip without TM_PLATFORM_TEST_DSN, and a silent skip reads as coverage. check: build vet fmt lint sqlc-check @$(GO) test ./... -race -count=1 -v > .check.log 2>&1; status=$$?; \ grep -E '^(ok|FAIL|\?)' .check.log || true; \ if [ $$status -ne 0 ]; then \ echo "--- FAILURES ---"; grep -E '^(---|[[:space:]]+---) FAIL' .check.log; \ rm -f .check.log; exit 1; fi; \ if grep -q -- '--- SKIP' .check.log; then \ echo "--- did NOT run (set TM_PLATFORM_TEST_DSN for the schema tests) ---"; \ grep -- '--- SKIP' .check.log; fi; \ rm -f .check.log # Not in `check`: fuzzing is time-boxed exploration, not a gate. The seed corpus runs as an # ordinary test on every `check`; this target is for going deeper on the decoder. fuzz: $(GO) test ./internal/ingest/ -run FuzzDecoder -fuzz FuzzDecoder -fuzztime 2m # Not part of `check`: it needs the network (the vulnerability database), and the battery must be # green on a bare clone offline. CI runs it as its own step (STACK_DECISIONS §5). vuln: $(GO) run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./...