Style & testing conventions
Audience: contributors writing or changing Go code in Aperture.
These are the house rules. The authoritative catalog is
CLAUDE.md at the
repo root; this page summarises what a reviewer will check.
Library-first
The product is the public Go packages at the module root. Every surface — the CLI, Twirp/HTTP, MCP — is a thin translator over one decision engine.
- No business logic in
cmd/aperture/.main.goonly assembles the binary and calls intointernal/cli. Decisions, mutations, and policy live in the root packages (engine/,service/,rules/, …). - New capability goes in the library first; the surface then exposes it.
Errors
Every failure that crosses a package boundary is an APERTURE_* coded error
from the root errors/ package.
- Never return bare
errors.New/fmt.Errorfacross package boundaries — wrap in a coded error viaerrors.New/Newf/Wrap/Wrapf. - Codes are SCREAMING_SNAKE,
APERTURE_-prefixed, declared inerrors/codes.go, listed inAllCodes, and each has aRegistryentry with aMessageand at least oneFixup(orFixupNotApplicable: true). - An error already carrying an
APERTURE_*code passes through verbatim — the wrappers never re-stamp it. Recover the code witherrors.CodeOf. - Do not leak cross-account data through error messages. Messages describe the failure, not another tenant’s data.
Adding a code is a recipe on the Extending Aperture
page, and is guarded by the TestCodesHaveFixups, TestRegistryHasNoOrphans,
and TestCodesAreScreamingSnakeNamespaced gates.
Pure-Go, no CGO, no Pulse
CGO_ENABLED=0is a hard requirement. Do not introduce a dependency that needs CGO (no geo/h3 or other C-linked packages).- No dependency on Pulse. The rules engine renders its AST to an
expr-lang/exprexpression and compiles it in-process. Aperture usesexpr-lang/exprdirectly; it does not import Pulse. - Storage is hand-written SQL over
modernc.org/sqlite(pure-Go) plus an in-memory implementation behind oneStorageinterface — no ORM, no sqlc, no migration tool.
Naming
- No predecessor references — no
Aperture2,LegacyX, or similar. Name for what a thing is now.
Testing
make testrunsgo test ./.... Tests are table-heavy and co-located with the code they exercise (*_test.go).- The server has
*_smoke_test.gosmoke tests; end-to-end tests live incmd/aperture/(e2e_test.go). - Include tests with new functionality. There are no follow-up-PR commitments for test gaps — cover it in the same PR.
- The performance NFR is asserted by
TestCheckNFRunderbench/, kept out ofmake test; run it explicitly (see gates).
Formatting & lint
Run make fmt, make vet, and make lint before opening a PR. make lint
degrades to go vet when no static analyser is on PATH locally, but CI runs
staticcheck, so fix what it reports.
The Update-Demand rule
Any change to a registered surface must ship the matching skills/*.md
document in the same PR — a surface change without its doc update is a
non-skippable CI failure. This is fully described in
The Update-Demand rule.