This guide is for the developer — the person who changes AdvisoryHub's
code and docs. It covers the local dev environment, tests, the code-quality
gates, and the commit conventions. Its companions: the
../specification/ set (what the system is —
authoritative), the ../operations/ manual (running the
service in production), and releasing.md (the maintainer
runbook for cutting a release).
The specification set in ../specification/ is the
single source of truth for what this system is and does. Read the
relevant file before making non-trivial changes, and cite INV-* IDs (from
invariant.md) in commits and PRs. Every
behavior change must update the affected spec file(s) in the same
commit/PR — a code/spec mismatch is a defect in whichever side drifted.
Any deviation from the spec requires explicit maintainer confirmation
before implementation.
docker-compose.yml is dev-only — every value it sets is a fixture,
no real secrets, and no .env file is required. The one variable that's
genuinely random (the OIDC client secret) is minted by the kanidm
bootstrap script and written to a file compose loads automatically.
First-run flow:
docker login dhi.io # one-time: app images base on DHI (free Docker account)
docker compose up -d kanidm # start the dev OIDC provider
bash dev/kanidm/setup.sh # one-time: cert, users, OAuth2 client
docker compose up # web + worker pick up the secretAfter that, plain docker compose up is enough.
Then in another terminal:
docker compose exec web python manage.py migrate
docker compose exec web python manage.py seed_demo \
--with-publish-repo /tmp/advisoryhub-pub.gitThe app is served at http://localhost:8000. Sign in with
alice@example.org / correcthorsebatterystaple (the demo users seeded
by dev/kanidm/setup.sh align 1:1 with what seed_demo creates Django-side)
and walk a draft → review → publish flow end-to-end.
To reset the dev environment:
docker compose down -v # wipes Postgres + kanidm volumes
docker compose build # down -v keeps cached images; rebuild after a Dockerfile/uv.lock change
docker compose up -d kanidm
bash dev/kanidm/setup.sh
docker compose upIf you use mise, it wraps the flows above so you don't have to remember the individual commands:
mise trust && mise run setup # install uv + prek, sync the locked .venv, wire git hooks
mise run kanidm-up # start the dev OIDC provider
mise run kanidm-setup # one-time: cert, users, OAuth2 client
mise run up # web + worker (full stack)
mise run migrate && mise run seedmise tasks lists them all (test, lint, fix, typecheck, ty,
check, build, reset, docs-build, docs-serve, …). mise is a convenience wrapper only: dev-tool versions live in
uv.lock, the Python version in .python-version, the bootstrap/chart/release
binaries (uv, prek, helm, kubeconform, git-cliff, trivy) are exact-pinned
in mise.toml, and CI runs these same tasks —
the raw uv / docker compose commands above stay canonical.
docker-compose.yml's x-django-env anchor is the canonical dev
configuration (reused by web and worker); don't edit env files for
dev. For production, .env.example documents every knob with
secret-vs-config markers — it is a reference for whatever secret manager
or platform manifest your deploy uses (Kubernetes Secrets, Docker Swarm
secrets, AWS SSM, …) and is not loaded by docker-compose. The full
env-var inventory with groups, defaults, and descriptions is in
architecture.md §7, and the step-by-step
operator guide (install, run, integrate, operate) is in
../operations/.
Tests run against PostgreSQL (the same engine as prod), so start it first:
docker compose up -d postgres # or `mise run up` for the full dev stack
DJANGO_SETTINGS_MODULE=config.settings.test pytestconfig.settings.test defaults to the local compose Postgres; set
TEST_DATABASE_URL to target a different host/port. --reuse-db (in
addopts) keeps the test database between runs — pass --create-db after a
migration change. Tests do not require a real OIDC provider, real email
delivery, or a real Git remote (the publication tests use a temporary local
bare repo and skip if git isn't on PATH). Testing strategy and conventions
are documented in architecture.md §9.
Lint, format, type, and Django checks run locally through
prek — the fast Rust reimplementation of
pre-commit — from .pre-commit-config.yaml. The
hooks invoke the Python tools out of the project venv, so they run the exact
versions pinned in uv.lock: what passes locally is what CI runs.
mise run setup # one-shot: installs uv+prek, syncs .venv, wires hooks
# …or by hand:
uv sync --extra dev # install the pinned ruff / mypy the hooks call
uv tool install prek # or: pipx install prek / cargo install prek / mise install
prek install # wire up the pre-commit AND pre-push git hooksOnce installed the hooks fire automatically:
- on commit — file hygiene (trailing whitespace, end-of-file, merge
markers, private-key detection, …) plus
ruff check --fixandruff format. - on push — additionally
mypy(+ django-stubs),manage.py makemigrations --check,manage.py check, and apip-auditdependency audit (mirrors CI's security job; needs network).
Run them on demand any time:
prek run --all-files # commit-stage checks
prek run --all-files --hook-stage pre-push # + type, Django & dependency-audit checks
prek run --all-files --hook-stage manual # advisory `ty` type-checkUpstream-verbatim files committed into the repo — htmx (static/htmx.min.js), the
Inter fonts, the ALTCHA widget (static/altcha/), the neoteroi docs CSS, and the
OSV/CSAF/CVE/CVSS JSON schemas (publication/schemas/) — are each pinned in a
*.VERSION (or SCHEMAS.VERSION) file recording the upstream version + SHA-256.
mise run verify-vendor (a commit-stage hook + CI) fails if a committed file drifts
from its pinned hash.
Updates are automated by a scoped, self-hosted Renovate workflow
(.github/workflows/renovate.yml) that tracks those .VERSION files plus the
mise.toml [tools] pins — Dependabot still owns Python/Actions/Docker/compose, so
the two never collide. On a new upstream release Renovate bumps the version and, for a
.VERSION bump, runs dev/update_vendored_assets.py (mise run update-vendor) to
re-download, rehash, and re-apply the OSV ECL- patch, then opens a PR.
mise-toolchain bumps arrive as one grouped, review-only PR.
Auto-merge policy: the JSON schemas are payload-validated by the publication test
suite (plus the OSV ecosystem drift guard), so a breaking change fails CI — their PRs
auto-merge on green. The frontend assets (htmx / ALTCHA / Inter / neoteroi) have
no behavioral tests, so their PRs are review-only — do a quick manual smoke (the
ALTCHA widget on /report/, an HTMX action, the OAD-rendered API docs page) before
merging. Re-vendor by hand any time with mise run update-vendor.
- Every commit message follows the Conventional Commits specification.
- Every commit is signed (
-S) and signed-off (-s). - Cite the
INV-*IDs your change touches in the commit message and PR description, and update the affected spec file(s) in the same commit/PR (see §1). - AI-assisted commits carry an
Assisted-by: <agent>:<model>trailer in the footer — e.g.Assisted-by: Claude:claude-sonnet-4-6— and never aCo-Authored-Bytrailer.
Releases are tag-driven: one signed vX.Y.Z tag produces the container
image, the Helm chart, the GitHub release, and the versioned documentation
site automatically. The maintainer runbook — version lockstep, cutting and
verifying a release, failure recovery — is releasing.md.
Everything under docs/ is published as a versioned site at
https://eclipse-csi.github.io/advisoryhub/ (.github/workflows/docs.yml):
latest is the newest release, numbered versions are immutable per-release
snapshots (deployed on every vX.Y.Z tag), and dev tracks main. The
site is built with MkDocs + mkdocs-material and versioned with
mike onto the gh-pages branch — the
branch is only the version-state store; the workflow deploys its content to
Pages itself (source: GitHub Actions), so nothing goes live on a bare
branch push.
mise run docs-serve # live preview at http://127.0.0.1:8001
mise run docs-build # strict build — the PR and deploy gate
mise run docs-deploy -- dev # rehearse a mike deploy into a LOCAL gh-pages branchLink rules (enforced by mkdocs build --strict on every docs PR):
- Inside
docs/, link relative to the current file and always to the.mdfile itself (../operations/README.md, not../operations/). - Anything outside
docs/(source files, charts, repo configs) gets an absolute GitHub URL — those files aren't part of the site.