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.py builds from the repository root, so links between README.md, CHANGELOG.md and docs/ work in the site as they do on GitHub.

  • docs/conf.py adds the site's navigation from its NAV table at build time, so the Markdown carries no Sphinx markup.

  • A new page in docs/ must be added to NAV.

  • -W makes every warning an error: a broken link or heading anchor, or a page missing from NAV.

  • CI's Docs job runs the same build and keeps the HTML as the docs-html artifact.

  • To publish, serve docs/_build/html from any static host; its index.html opens 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-data on GitHub is docs/data.html#personal-data in the site. The sidebar and links within the site use them too (GitHubAnchors in docs/conf.py).

  • docs/requirements.txt pins 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 through internal/source.

  • No foreign key, view, function or trigger in aveloxis_cve may 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 INSERT names its ON 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.queryRowWrite and s.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

TestNoImportsFromAveloxis

internal/archtest

No Go import of Aveloxis

TestOnlySourceNamesAveloxisSchemas, TestSourceNeverWrites

internal/archtest

Only internal/source names Aveloxis schemas, and it only reads

TestOnlyStoreAndMigrationsWrite

internal/archtest

Only the store and migrations write SQL

TestInsertsHaveNamedOnConflict

internal/archtest

Every INSERT names its arbiter

TestNoStructuralSkips

internal/archtest

Skips only through internal/testdb

TestGoSourcesAreASCII

internal/archtest

ASCII-only Go sources

TestEveryStoreWriteGoesThroughTheFence

internal/store

Every store write is fenced

TestMigrationsNeverReferenceAveloxisSchemas

internal/db

No cross-schema object in migrations

TestMigrationInsertsAreIdempotent

internal/db

Migration INSERTs are idempotent

TestDataDocCoversEverySchemaColumn

internal/db

data.md documents every table, view and column

TestSchemaDiagramMatchesSchema

internal/db

schema.md draws every table, column, type, key, foreign key and view dependency

TestAnalysisQueriesRun, TestAnalysisGuideMapsEveryQuery

internal/db

Every analysis query runs read-only on the seed and returns exactly its reviewed result (testdata/analysis_golden.txt), and the guide links each one

TestPathRules

internal/db

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

TestGoldenTextRefusesPartialResults

internal/db

-update rewrites testdata/analysis_golden.txt only from a complete, passing run

TestRunPyConnectionSettings, TestRunPySamplesFollowTheDefaultBranch, TestRunPyMainExitStatus

internal/db

run.py connects as aveloxis-cve does, samples the current default branch's series, reads psql's output byte for byte in UTF-8 (a carriage return in a path and an inherited PGCLIENTENCODING included), refuses a non-UTF-8 filesystem encoding, and exits 1 after its report when a query fails

TestDocsLinksResolve, TestWithoutCode

internal/db

Every relative link and anchor in docs/ and the README resolves, outside code, and no code fence is left open

TestEveryConfigKeyIsInTheExampleAndTheDocs, TestExampleConfigShowsEffectiveDefaults

internal/config

Every config key is in the example and the docs, with its real default

TestRefreshUndoesAgreesWithMigration16

internal/store

The Go and SQL undo rules agree

TestResolveAuthorsReadsAveloxisOnlyThroughIndexes

internal/source

Author resolution reads Aveloxis only through indexed equality lookups (under Aveloxis 0.29.141's indexes)

TestSkippedMatchesRealSCC

internal/labor

The walk emulation agrees with the real scc binary

Changing the schema

  1. 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.

  2. Reference only aveloxis_cve objects.

  3. Document every new table, view or column in data.md and draw it in schema.md. The tripwires list what is missing.

  4. If an analysis query should use the new data, add it to analysis/, and extend internal/db/testdata/analysis_seed.sql so it returns rows.

  5. Operators apply the change with aveloxis-cve migrate. A command that needs a new table should say so on SQLSTATE 42P01 (see store.stopErr).

Review

A change is complete only after:

  • the full suite has passed with AVELOXIS_CVE_TEST_DB set;

  • 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.