# Local gates — run before every commit. CI (dagnats, later) runs the same.
#
# "the same" is true again as of this line. It was not: CI (ci/runner.json)
# also runs `gofmt -l cmd internal`, so a landing
# could pass every gate here and go red in CI — which is exactly what
# happened on 2026-09-21, over a test file that ended without a
# newline. A documented gate that is weaker than the real one is worse
# than no gate, because a green run gets read as CI-ready.
.PHONY: check fmt test race vet build fixtures fixtures-real spec-lint docs dev
check: fmt vet test race build
# gofmt -l exits 0 even when files need formatting; its OUTPUT is the
# verdict. So this fails on any, and prints what to run rather than
# leaving the reader to work out that `gofmt -w cmd internal` is the
# answer.
fmt:
@unformatted="$$(gofmt -l cmd internal)"; \
if [ -n "$$unformatted" ]; then \
echo "$$unformatted"; \
echo "these need gofmt: run 'gofmt -w cmd internal'"; \
exit 1; \
fi
# Spec correctness gate (0003 §2): lint the generated OpenAPI document with
# an off-the-shelf validator. Dev/CI tool only — never a runtime dep.
spec-lint:
go run ./cmd/quarry spec > /tmp/quarry-spec.json
go run github.com/daveshanley/[email protected] lint --no-banner --no-style --fail-severity error /tmp/quarry-spec.json
# API reference, generated from the op registry (0003: docs are a phase
# exit criterion). TestDocsCurrent fails the build when this is stale.
docs:
go run ./cmd/quarry docs > docs/api.md
# What a first visit costs on the wire, per asset, against a running
# forge: make wire URL=https://zeitforge.app/fleet/exceptions
wire:
scripts/wire-check.sh $(URL)
# Synthetic disposability fixture (fast; the CI corpus).
fixtures:
fixtures/build-synthetic.sh
# Real corpus (sqlite, tcl) — large clones, opt-in.
fixtures-real:
fixtures/build-real.sh
test:
go test ./...
# The race detector over the packages whose invariants only a data
# race can break. CI runs this as its own step (quarry.race) so a race
# is named as one in the run log. The cheap ones repeat; internal/web
# and internal/index take over a minute a pass, so they run once to
# keep the step near three minutes.
race:
go test -race -count=3 ./internal/children/ ./internal/xfer/
go test -race -count=3 -run 'Stop|Teardown' ./cmd/quarry/
go test -race -count=1 ./internal/web/ ./internal/index/
vet:
go vet ./...
build:
go build ./...
# Third-party licence inventory served at /licenses.
licenses:
go run ./cmd/licensegen > internal/legal/licenses.json
# Dev loop: rebuild and restart the server whenever a .go file changes
# (wgo: go install github.com/bokwoon95/wgo@latest).
#
# Per-machine settings come from .env.local, which is not committed; copy
# .env.example to start one. dev.local.mk is the previous name and is
# still read, so an existing checkout keeps working.
-include .env.local
-include dev.local.mk
# Each corpus repository is named once in QUARRY_CORPUS_REPOS and its path
# derived, rather than repeating --repo name=path twenty-odd times.
corpus_repo_flags = $(foreach r,$(QUARRY_CORPUS_REPOS),--repo $(r)=$(QUARRY_CORPUS)/$(r))
extra_repo_flags = $(foreach r,$(QUARRY_EXTRA_REPOS),--repo $(r))
# Only the settings that were given become flags: an empty key is left out
# entirely rather than passed as an empty string, which the server would
# read as a deliberate value.
optional = $(if $(2),--$(1) $(2))
# ?= so a .env.local that still sets DEV_ARGS wholesale keeps winning.
DEV_ARGS ?= \
$(call optional,addr,$(QUARRY_ADDR)) \
$(call optional,data,$(QUARRY_DATA)) \
$(call optional,oltp,$(QUARRY_OLTP)) \
$(call optional,repos-root,$(QUARRY_REPOS_ROOT)) \
$(call optional,tracker,$(QUARRY_TRACKER)) \
$(call optional,dagnats,$(QUARRY_DAGNATS)) \
$(call optional,notify,$(QUARRY_NOTIFY)) \
$(call optional,notify-key,$(QUARRY_NOTIFY_KEY)) \
$(call optional,errorlog,$(QUARRY_ERRORLOG)) \
$(corpus_repo_flags) \
$(extra_repo_flags)
dev:
wgo run ./cmd/quarry serve $(DEV_ARGS)
# Print the composed flags without running anything — the fastest way to
# see what .env.local actually amounts to.
dev-args:
@echo $(DEV_ARGS)