Testing and CI Contracts
- Type: Reference
- Status: Current
- Scope: Validation selection and the repository's current local and Forgejo CI gates
- Canonical sources:
AGENTS.md;README.mdchange lifecycle;flake.nix;treefmt.nix;justfiles/deploy.just;.forgejo/workflows/attic-cache.yml;.forgejo/workflows/ai-review.yml;scripts/attic-cache-build.sh;tests/; Safe Testing and Deployment- Last verified: Source commit
556df88494686003b1c4f20c8e0b99b5afc16a6e(2026-09-12); source HEADb2831d33132967b4c4fb69e6068b2b05fff8fdffchanges onlyflake.lock. Local recipes, flake outputs, and Forgejo workflow gates checked 2026-09-13; no CI run or host deployment was performed.- Review triggers: Changes to
AGENTS.mdvalidation policy, flake checks/formatter, test scripts, CI path filters/jobs, cache-build script, deploy recipes, or canary and evidence requirements
Purpose and boundary
Choose validation by the behaviour a change affects and know what each gate actually proves. This page describes the engineering contract; Safe Testing and Deployment owns activation, auto-update holds, Electra specialisations, and operational rollback. Passing CI is not proof that a service works for its clients or that the intended host has deployed the change.
Select the gates
| Change surface | Local evidence before review or commit | Additional evidence before calling it live |
|---|---|---|
| Wiki, README, or other documentation only | Focused source/prose/link review; no Nix build merely for prose | Published page/commit and accurate provenance |
| Nix modules, hosts, packages, flake inputs, or options | Stage new .nix files; just check; build affected host closure(s); inspect intended diff |
Appropriate canary with just test, real user/service check, then deployment evidence |
| Shell scripts, Just recipes, workflow YAML, updater or policy logic | Relevant lint and regression tests, including cross-boundary tests; just check too when Nix outputs or policies are affected |
Exercise documented command and systemd path; multi-host/update changes need genuine deployment and no-op follow-up |
| Stateful, security, network, boot, or cross-host changes | All applicable gates plus explicit rollback/recovery and target-specific checks | Required MR checks, controlled canary, data/client/restore evidence as applicable |
These are minimums from AGENTS.md, not a claim that a file extension proves
safety. A .nix module can change a service's shell launcher or security
boundary; a shell script can change updater policy. Review diagnostic warnings
rather than suppressing them. A cross-cutting, service, CI, security, or
multi-host change normally uses an issue, feature branch, and MR with the
same milestone; a bounded one-application or module toggle can go directly
to main after targeted validation. Close an issue only after any required
live evidence is recorded.
Local commands and what they prove
just check
nix fmt -- --ci
nix build .#nixosConfigurations.HOST.config.system.build.toplevel --no-link
just check calls nix flake check. The flake currently exposes formatting
and an auto-update-state-machine check on x86_64-linux; the latter comes
from the pinned standalone updater. The formatting check uses treefmt.nix
(nixfmt and shfmt). nix fmt -- --ci checks formatting without rewriting
files. New untracked .nix files must be staged first because a Git-backed
flake omits them. The explicit nix build constructs one registered host
top-level; replace HOST with the affected target. It does not activate it,
prove mount/secret availability, or exercise client traffic.
For scripts and workflows, run the relevant repository regressions, not an
undifferentiated test folder. Current examples include
tests/nixos-update-remediation-regressions.sh,
tests/nixos-pin-lifecycle-regressions.sh, and the deployment wrapper tests
tests/nixos-deploy-regressions.sh and
tests/nixos-deploy-wrapper-regressions.sh. Verify their prerequisites and
the precise affected contract. A test existing under tests/ does not mean
the Forgejo workflow runs it automatically.
For the activation path, inspect just build-diff, create a remote
auto-update hold where required, then use just test on an affected NixOS
host. This test activation does not add a boot-menu generation and preserves
Electra's current specialisation unless explicitly changed. Check the
documented user-facing command/UI and the service/socket/timer plus journal
when both are interfaces. Safe Testing and
Deployment has the ordered commands and
recovery checks. Nova is currently a Fedora CI runner, not a registered NixOS
target; CI running there is not a Nova NixOS deployment.
What Forgejo CI currently does
The Build and Push Attic Cache workflow runs on main pushes and pull
requests only when paths in .forgejo/workflows/attic-cache.yml match. It
also has scheduled and manually dispatched runs. Its test job on Nova runs
ShellCheck for scripts/*.sh and tests/*.sh, Actionlint for Forgejo
workflows, forgejo-runner validate, the remediation publisher bootstrap
check, the two updater/pin-lifecycle regression scripts named above, and
nix flake check. A failed test job blocks the dependent build job.
The cache build job is deliberately skipped on pull requests. On eligible
non-PR runs, scripts/attic-cache-build.sh builds current host closures or
update candidates and pushes cache outputs. Therefore a green PR test job
does not establish that every affected host top-level built; perform
targeted local builds and record their revision. The workflow's path filters
also mean a change outside them may have no cache-workflow PR check at all.
Do not infer coverage merely from a green or absent badge.
The separate .forgejo/workflows/ai-review.yml runs an advisory PR review
on same-repository pull requests and manual dispatch. It is not a substitute
for lint, Nix checks, targeted builds, or live evidence. The MR must identify
which checks were required, which ran, and what remains for deployment.
Evidence, failures, and maintenance
- Record the exact source revision, commands, affected hosts, outcomes, warnings, CI run/MR, canary, and any remaining live gate in the issue/MR. A test of a different commit or stale target checkout is not evidence for the proposed revision.
- If
just checkfails, first verify Git staging, formatting, option errors, and the named flake check. If a host build fails after flake check passes, investigate the exact host closure, input/policy and package failure. If CI is absent, inspect path filters and runner state before declaring success. - If a test passes but the client path fails, the acceptance gate is still open. Check the activated generation, service logs, network path, secrets, mounts, and relevant external dependencies.
- Before activation, revert the reviewed source if needed. After activation, use the rollback and recovery procedure appropriate to the change; generation rollback may not reverse data or schema migrations. Do not close a multi-host or auto-update issue until a genuine deployment and no-op follow-up are recorded.
- When adding a test, decide whether it is a local-only diagnostic, a flake check, or a Forgejo job. Update CI path filters and commands deliberately when automatic coverage is intended. Keep this page aligned with the actual workflow rather than treating its present test list as timeless policy.
Canonical source map
| Concern | Source |
|---|---|
| Gate selection, MR and live-evidence policy | AGENTS.md, README.md Change Lifecycle |
| Flake and formatter checks | flake.nix, treefmt.nix |
| Local build, diff, test, and deployment semantics | justfiles/deploy.just, Safe Testing and Deployment |
| CI triggers, lint, regressions, Nix gate, cache build | .forgejo/workflows/attic-cache.yml, scripts/attic-cache-build.sh |
| Advisory review trigger and trust boundary | .forgejo/workflows/ai-review.yml |
| Regression implementation | tests/ |