Development¶
How aveloxis-cve is built, tested and changed. The rules here are adopted from Aveloxis (../aveloxis/CLAUDE.md, ../aveloxis/.claude/rules/), and the tripwire tests below enforce most of them.
Build and test¶
go build ./...
go test ./... # unit tier
AVELOXIS_CVE_TEST_DB="postgres:///postgres?host=/tmp" go test ./... # + DB tier (required before any change is done)
gofmt -l . && go vet ./... && staticcheck ./... && golangci-lint run ./...
AVELOXIS_CVE_TEST_DB. A DSN for a superuser on a disposable cluster, never a production one.
Each DB-tier test creates its own scratch database
cvet_<random>and drops it afterwards, even when the test fails part-way.Some tests create cluster-wide roles
cvet_role_<random>and drop them.A run killed midway can leave either behind. Drop them by hand: never anything named
aveloxis*.
Without the DSN, DB-tier tests skip through internal/testdb, the only allowed way to skip. Under CI ($CI set), a missing tool fails the run instead.
git. Tests build their repositories with internal/gittest, isolated from your global and system git configuration.
CI. .github/workflows/ci.yml runs the lint gates and the full suite (both tiers, PostgreSQL 18 in a service container) on every pull request and push to main, once with scc 3.7.0 (the version Aveloxis runs) and once with scc 4.1.0. GitHub sets CI=true, so there a missing tool fails a test instead of skipping it.
Documentation site. The Markdown in docs/, with README.md and CHANGELOG.md, builds into an HTML site with Sphinx (MyST for Markdown, Mermaid for the diagrams). It needs Python 3.12 or later:
python3 -m venv .venv-docs && .venv-docs/bin/pip install -r docs/requirements.txt
.venv-docs/bin/sphinx-build -c docs -W --keep-going . docs/_build/html # open docs/_build/html/index.html
docs/conf.pybuilds from the repository root, so links betweenREADME.md,CHANGELOG.mdanddocs/work in the site as they do on GitHub.docs/conf.pyadds the site's navigation from itsNAVtable at build time, so the Markdown carries no Sphinx markup.A new page in
docs/must be added toNAV.-Wmakes every warning an error: a broken link or heading anchor, or a page missing fromNAV.CI's
Docsjob runs the same build and keeps the HTML as thedocs-htmlartifact.To publish, serve
docs/_build/htmlfrom any static host; itsindex.htmlopens the README. The build writes.nojekyll, so GitHub Pages serves the theme and search files as they are.Heading anchors in the site are GitHub's, so
docs/data.md#personal-dataon GitHub isdocs/data.html#personal-datain the site. The sidebar and links within the site use them too (GitHubAnchorsindocs/conf.py).docs/requirements.txtpins every package, not just Sphinx and its extensions, so the build only changes when that file does. To update it, see its header.
Collation. Scratch databases are created with ICU en-US collation whatever the cluster's default (testdb.New), so golden results that order text agree between macOS, glibc and CI. The test cluster must be PostgreSQL 15 or later, built with ICU, with a UTF-8 or C default locale (LC_COLLATE and LC_CTYPE); otherwise every DB-tier test fails, and the failure names this requirement as a possible cause.
Lint versions. Use the current staticcheck and golangci-lint. An older golangci-lint built with an older Go may panic on this toolchain. go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run ./... avoids that.
Rules¶
Zero coupling.
Write only
aveloxis_cve, and read Aveloxis only throughinternal/source.No foreign key, view, function or trigger in
aveloxis_cvemay reference an Aveloxis object.Never import Aveloxis Go code, and never use Aveloxis's clones.
Only Aveloxis creates objects (indexes included) in its schemas. Never create them, or suggest an operator create them.
Test first.
Every new error or edge branch gets a behavioural test that drives the failing input. Write the test first and see it fail ("red first").
Comparisons at a boundary state which side of the equality is legal.
Versioning. Every code change bumps internal/db/version.go (ToolVersion) and adds a CHANGELOG.md entry in the same change.
SQL.
Every
INSERTnames itsON CONFLICT (...)arbiter.A failed lookup is an error, never "not found". Only a typed not-found means absent.
Decide on typed errors and SQLSTATEs, never on error text.
Identity. Never fabricate it:
an ambiguous email-to-contributor match stays NULL;
merged contributors (
cntrb_deleted = 1) are never chosen.
Config. Defaults live in one place (config.Default()). Every key appears in aveloxis-cve.example.json and configuration.md, and is tested end to end.
Skips. No structural skips: tests skip only through internal/testdb, for a missing DSN or tool.
Scratch names. Scratch databases are cvet_*, and scratch roles cvet_role_*.
Destructive actions. Destructive database actions in tests stay inside the test's own scratch database.
Watchdogs. Watchdogs only observe. Nothing kills or changes state on its own.
Source text. Go sources are ASCII only.
Concurrency.
Every write made under a repository lock or the labor gate goes through the store's fence (
internal/store/fence.go). The fence proves, in the write's own transaction, that the lock is still held.Store methods use
s.exec,s.queryRowWriteands.begin, never the pool directly, for writes.
Tripwires¶
These tests fail when a rule is broken, so the rules hold without anyone remembering them.
Test |
Package |
Enforces |
|---|---|---|
|
|
No Go import of Aveloxis |
|
|
Only |
|
|
Only the store and migrations write SQL |
|
|
Every INSERT names its arbiter |
|
|
Skips only through |
|
|
ASCII-only Go sources |
|
|
Every store write is fenced |
|
|
No cross-schema object in migrations |
|
|
Migration INSERTs are idempotent |
|
|
data.md documents every table, view and column |
|
|
schema.md draws every table, column, type, key, foreign key and view dependency |
|
|
Every analysis query runs read-only on the seed and returns exactly its reviewed result ( |
|
|
The path rules of CVE-16/17 and LAB-13/14 classify every alternative they name as documented, and the copies of each rule agree |
|
|
|
|
|
|
|
|
Every relative link and anchor in |
|
|
Every config key is in the example and the docs, with its real default |
|
|
The Go and SQL undo rules agree |
|
|
Author resolution reads Aveloxis only through indexed equality lookups (under Aveloxis 0.29.141's indexes) |
|
|
The walk emulation agrees with the real scc binary |
Changing the schema¶
Add the next numbered migration,
internal/db/migrations/NNNN_name.sql. Migrations are embedded, applied in order, each in its own transaction with its ledger row. They must be safe to apply once on any earlier schema.Reference only
aveloxis_cveobjects.Document every new table, view or column in data.md and draw it in schema.md. The tripwires list what is missing.
If an analysis query should use the new data, add it to analysis/, and extend
internal/db/testdata/analysis_seed.sqlso it returns rows.Operators apply the change with
aveloxis-cve migrate. A command that needs a new table should say so on SQLSTATE 42P01 (seestore.stopErr).
Review¶
A change is complete only after:
the full suite has passed with
AVELOXIS_CVE_TEST_DBset;gofmt, vet, staticcheck and golangci-lint are clean;
a fresh-context adversarial review of the whole diff has passed, against the lenses in
../aveloxis/docs/contributing/review-lenses.md.
Each finding is checked, then either fixed red-first or declined with the reason written at the site. Review rounds repeat until one finds nothing. The hand-off lists each round's findings in a table.