Validation Guide
Contributor validation guidance for Vaulthalla changes.
Validation Guide
This is the "what should I run before opening a PR?" guide.
Run the narrowest checks that actually prove your change. Then be honest about what you did and did not validate.
Quick Reality Check
Useful repo helpers:
1bash tools/dev/verify.sh doctor # toolchain and version-pin sanity2bash tools/dev/verify.sh # infer checks from your uncommitted changes3bash tools/dev/verify.sh web # typecheck + lint4bash tools/dev/verify.sh release # version drift + release-tooling tests5bash tools/dev/verify.sh packaging # packaging contracts, lab and board tooling tests (needs python3-yaml)6bash tools/dev/verify.sh all # everything except the destructive integration harness
These profiles are helpful shortcuts, not a substitute for subsystem-specific judgment.
Docs-Only Changes
Docs are validated in the release pipeline with pmdocs validate --source docs. Locally, run:
1bash tools/dev/verify.sh docs # Payload Markdown checks on changed pages, then pmdocs validate when installed
For docs-only PRs, do this at minimum:
- verify every command against the repo
- verify every referenced path exists
- avoid claims about infrastructure or workflows that are only planned
- read the rendered markdown in your editor and check relative links
If the change is truly docs-only, code tests are usually not required.
The docs checks validate structure and directives. They do not prove that commands, paths, or described behavior are correct, so that review is still on you.
Web And Frontend Changes
Run:
1cd web2pnpm test
That runs the repo's web check path:
pnpm run typecheckpnpm run lint
Useful extra validation:
1cd web2pnpm dev
Use that for manual checks when you changed UI behavior.
You can also run the repo helper from the root:
1bash tools/dev/verify.sh web
S3 Gateway Browser And Smoke Validation
The S3 Gateway E2E harness sources the standard local/dev env files before it reports missing DB, R2, or browser-login settings:
1source tools/e2e/load_env.sh2vh_e2e_redacted_env_report
The loader reads, when present:
$HOME/.bashrc./.bashrc./deploy/bashrc./deploy/vaulthalla.env
The redacted report shows whether VH_TEST_DB_*, VAULTHALLA_TEST_R2_*, and VAULTHALLA_E2E_* are set without printing secret values.
The focused browser suite can auto-start the local Next dev server when the base URL is localhost and VAULTHALLA_E2E_NO_WEB_SERVER is not set:
1pnpm --dir web run test:e2e:s3-gateway
The Playwright suite seeds its own admin login during global setup. For local/dev runs it assumes the Vaulthalla backend is already running in dev mode, creates a fresh e2e_s3gw_* admin user in the configured local dev DB, exports the generated username/password to the test workers, and stores the password only under test-results/s3-gateway-e2e/e2e.env with private permissions. Set VAULTHALLA_E2E_SKIP=1 only when you are intentionally skipping the browser suite.
For data-plane validation, use the smoke wrapper:
1tools/smoke/s3_gateway_e2e.sh
The wrapper sources the env loader, starts the web dev server when needed, attempts to enable/start the S3 gateway before declaring it unreachable, runs the self-seeding Playwright S3 Gateway suite, and then runs tools/smoke/s3_gateway_scoped_budget_smoke.sh --local-only. Set S3_GATEWAY_ENDPOINT when the gateway is not on http://127.0.0.1:39000.
Useful wrapper options:
1tools/smoke/s3_gateway_e2e.sh --local-only2tools/smoke/s3_gateway_e2e.sh --no-start-web3tools/smoke/s3_gateway_e2e.sh --keep-processes4tools/smoke/s3_gateway_e2e.sh --web-timeout 180
Remote R2/S3 validation is opt-in:
1tools/smoke/s3_gateway_e2e.sh --require-remote --prefix s3-gateway-e2e/manual-$(date -u +%Y%m%dT%H%M%SZ)
Remote smoke uses existing S3_GATEWAY_SMOKE_* and VAULTHALLA_TEST_R2_* settings. It deletes only the unique prefix it was given. If cleanup fails, the script prints the exact prefix to remove manually.
The scoped smoke has explicit budget-denial modes:
1tools/smoke/s3_gateway_scoped_budget_smoke.sh --budget-denial synthetic2tools/smoke/s3_gateway_scoped_budget_smoke.sh --require-remote --budget-denial actual-upstream3tools/smoke/s3_gateway_scoped_budget_smoke.sh --require-remote --budget-denial both
Use synthetic for deterministic local validation. It enables enforce_budget_for_local_requests, uses nominal gateway-local synthetic rates, and asserts local/cache ledger rows have synthetic=true. Use actual-upstream only with remote env available; it seeds remote-only objects, imports the remote index, validates remote-only GET behavior, and checks AccessDenied price-budget plus SlowDown request-budget denial. If the local/dev pricing catalog for the live provider is unavailable, the smoke reports that synthetic=false remote_download price-ledger capture is catalog-gated; the DB-backed accounting tests cover that ledger assertion with a seeded catalog. Do not use a local-first PUT loop as proof of upstream provider spend.
For final S3 Gateway merge readiness, run:
1tools/smoke/s3_gateway_merge_ready.sh
The wrapper runs shell syntax checks, meson compile -C build, the DB-backed S3 gateway/cost/pricing filter, web unit tests, Playwright S3 Gateway E2E, local smoke, remote smoke when R2 env exists, and git diff --check. It writes test-results/s3-gateway-e2e/merge-ready-report.txt with pass/fail per stage, exact commands, env-source status, E2E credential status, smoke prefixes, and R2 cleanup result.
Do not report "DB env missing", "E2E credentials missing", "web stack unreachable", or "S3 gateway unreachable" until the loader, provisioner, and startup attempts above have run and their redacted diagnostics/log paths are captured.
C++ Core Changes
Use the CI-style Meson build path:
1meson setup build --buildtype=debug -Dbuild_unit_tests=true -Dinstall_data=false2meson compile -C build3meson test -C build
If your change is in core code and you did not build it on Linux, say so.
Zero-Warning Builds On Both Platforms
Every pushed C++ change must configure and build on Linux and macOS with nothing but progress output:
1meson setup build -Dwerror=true -Dbuild_unit_tests=true 2>&1 | tee setup.log # macOS: source tools/macos/env.sh first2bash tools/dev/quiet-build.sh build setup.log
CI runs the same check on both platforms and fails on any warning, note or Meson deprecation. If you only have one platform, say which one you built on; CI covers the other. See Builds Must Be Silent.
macOS Service And Package Changes
For deploy/launchd/* or tools/macos/pkg/*:
1python3 -m unittest tools/contracts/test_macos_launchd_contract.py tools/contracts/test_macos_caddy_contract.py2tools/macos/candidate.sh stage && tools/macos/candidate.sh pkg3sudo tools/macos/candidate.sh accept
The contract tests run the real scripts against stubs and also run on Linux CI. Only the last command installs the package as the real system service and proves it end to end (account and permissions, the LaunchDaemons, an encrypted vault, crash and database-outage recovery, CLI access, vh setup caddy, an upgrade). Do it on a Mac you may change.
FUSE And Filesystem Changes
FUSE work needs more than a compile.
Use the Linux integration path when possible:
1make itest
Also capture the manual behavior you checked, for example:
- mount comes up cleanly
- file operations behave as expected
- unmount and shutdown behavior are sane
- no obvious regressions in permissions or cache behavior
FUSE changes without Linux runtime validation are incomplete.
CLI Changes
If you changed CLI parsing, usage text, shell protocol behavior, or lifecycle commands:
1meson setup build --buildtype=debug -Dbuild_unit_tests=true -Dinstall_data=false2meson compile -C build3make itest
For help or manpage-related work, also inspect the affected usage definitions under core/usage/* and confirm the output is coherent.
Packaging Changes
Run:
1bash tools/dev/verify.sh release packaging2make deb # local release rehearsal: builds and validates the package from a worktree of HEAD (commit first)
Then describe the lifecycle validation you performed:
- install
- upgrade if relevant
- remove
- purge
- service start and stop behavior
- config and local-state preservation
If you did not test package lifecycle on a Linux host or VM, say so.
Database Or Schema Changes
Schema and DB-layer changes need more than "the SQL looked fine."
At minimum:
1make itest2make test
If packaging or release behavior is involved, also run:
1bash tools/dev/verify.sh packaging2make deb
Use a disposable PostgreSQL-backed environment when you can. Document migration or upgrade assumptions clearly.
Security-Sensitive Changes
Before you even get to validation, read Security-Sensitive Work.
If the change was approved for public work, do not rely on partial validation. Sensitive changes should include:
- focused tests or reproducible manual checks
- clear impact description
- explicit note about anything you could not validate
Release Changes
Releases use vl-release (vlr), configured by release.toml. Run:
1bash tools/dev/verify.sh release # vlr check + vlr version check2vlr prepare --dry-run # preview the release notes and Debian changelog entry
If the change affects packaging outputs, add make deb.
Changes users or operators would notice also update .release/RELEASE_NOTES_NEXT.md, and packaging-facing ones
.release/CHANGELOG_NEXT.md, in the same pull request. Never edit debian/changelog, RELEASE_NOTES.md or version
numbers by hand.
What To Put In The PR
A good validation note says:
- what you changed
- what commands you ran
- what manual checks you performed
- what you could not test
Examples:
- "Docs-only. Verified all commands and paths against the repo. No code paths changed."
- "Ran
cd web && pnpm testand manually checked the updated admin page underpnpm dev." - "Ran
make testandmake iteston Linux. Did not validate upgrade behavior."
That level of honesty is enough. Hand-wavy claims are not.
