Three validation tiers, cheapest first. All live in scripts/ci/ and run
identically in CI (as required workflow steps) and locally (via make).
assert_image.sh)Loop-mounts the image read-only and runs table-driven assertion lists
from scripts/ci/assertions.d/ (common.txt always; extra tables per
variant). One assertion per line:
file_exists <path> file_absent <path>
symlink_valid <path> unit_enabled <unit> <wants-dir>
user_exists <name> <uid> linger_enabled <user>
json_valid <path> grep_file <path> <regex>
To add a check, add a line to a table. Do not edit the script. Every provisioning change should land with a matching assertion.
chroot_checks.sh)Executes the image’s own arm64 binaries via qemu-user binfmt (no pid1, no audio, no network):
scripts/ci/imports.d/<variant>.txtpip check (dependency-graph consistency).service file’s Exec* binaries exist in the image
— this is the check that catches a helper script renamed upstream but
not in the unit file.qemu_boot_test.sh)Boots the image on the qemu virt machine with the image’s own kernel,
drives the serial console (expect): waits for login, asserts all ovos-*
user units reach active within 120 s, and fails on any
journalctl -p err line not matched by journal-allowlist.txt. Every
allowlist entry must carry a comment saying why it is benign. The serial
log is always kept as an artifact.
Runs nightly against the newest DEV base and on demand
(workflow_dispatch with an image URL). Not covered: Pi firmware,
device-tree, HATs, real audio — see the
release checklist.
| Workflow | Tiers |
|---|---|
| every image build (base + languages) | 1 + 2, gating release upload |
smoke_nightly.yml |
3 |
shellcheck.yml |
lint on every PR |