Clean-System Testing Walkthrough
This workflow records reviewable evidence for documentation commands on a
clean system. It never turns the documentation inventory into a script to run
blindly. Only explicit cases in test/docs-walkthrough/cases.json may execute;
unmapped executable and manual-only inventory rows remain visible as pending.
Use this workflow when collecting new clean-system execution evidence. It is not the completion gate for a documentation-only audit: source review, parser conformance, existing behavior tests, and package metadata can establish many documentation facts without replaying the commands. A pending execution record does not by itself identify a documentation defect.
Safety boundary
Booted scenarios run only in a disposable guest you provision and snapshot.
The runner does not create a VM. Before it will mutate the guest, it requires
the regular root-owned mode-0644 marker
/etc/facelock-walkthrough-guest.json with this shape:
{
"disposable": true,
"guest_id": "unique-per-guest-id",
"os": "debian-13",
"image": "exact-matrix-image@sha256:full-image-digest",
"init": "systemd",
"snapshot": "pristine-snapshot-id",
"level": "booted-vm",
"hardware": []
}
Match os, image, and init to the selected scenario. The runner also
verifies virtualization and refuses shared 9p, virtiofs, NFS, or CIFS mounts;
protected PAM/bus mounts; and unexpected camera or TPM devices. Hardware
scenarios must declare intentionally attached devices instead of inheriting
them accidentally. A marker is an authorization for this disposable guest,
not something to place on a workstation.
Inspect and validate the catalog
These repository-local commands do not run documented install/auth commands:
python3 test/docs-walkthrough/run.py list
python3 test/docs-walkthrough/run.py check
python3 test/docs-walkthrough/run.py report
list shows scenario IDs, check validates definitions, source pins, and total
mapping, and report compares explicit case mappings with the complete
documentation inventory. The separate walkthrough unit tests exercise safety
guards. Use refresh only when intentionally refreshing the checked-in
inventory and generated manual sections derived from documentation:
python3 test/docs-walkthrough/run.py refresh
git diff -- test/docs-walkthrough/cases.json
git diff -- test/docs-walkthrough/manual-sections.json
Review changed expectations and manual gate classifications before committing either generated file. A refreshed mapping is not evidence that any command was executed or reviewed.
The catalog includes repository and direct-package cases for APT, RPM/COPR, AUR, source, NixOS, OpenRC, runit, and s6, plus first setup, daemon/oneshot auth, desktop lock, physical TPM, GPU, and Y16 cases. Fixed adapters are reviewed route probes with source-context references, not literal replay of the referenced line. Literal source rows become ordered manual-section candidates. Listing or generating a case is not a claim that its expectations were reviewed or that it passed.
Pin release identity
Every run consumes an identity JSON rather than inferring binary publication from a tag or local build. For a GitHub release-asset channel, generate the readiness report for the intended release and channel:
RELEASE_TAG=v0.2.0-alpha.4
CHANNEL=github-alpha
READINESS_FILE=/tmp/facelock-walkthrough-readiness.json
python3 test/docs-walkthrough/run.py readiness --release "$RELEASE_TAG" --channel "$CHANNEL" --output "$READINESS_FILE"
The identity must bind release, normalized version, exact native package
version, a 40-hex artifact_commit, channel, runtime policy, and immutable
artifact evidence: name, URL, positive size, and 64-hex SHA256. GitHub binary
release channels additionally require a positive release asset ID. Source/Nix
identities instead name the exact GitHub tag archive; a tag archive does not
require a GitHub Release. The current readiness helper nevertheless queries
the Release API for these channels too, so its missing-release result is not a
valid source-archive availability check. Check the tag resolution and archive
identity separately; the source adapter does not require Release metadata.
The evidence record separately binds harness_sha256 and
harness_tree_dirty. Public-repository identities additionally bind the
downloaded package digest and repository URL, plus APT suite/signing-key
digest, COPR chroot, or AUR commit as applicable. An AUR source-built package
may omit the expected package digest; its evidence instead records the built
payload digest and the verified recipe commit. Every other repository channel
requires the expected package digest. artifact_commit is an asserted input
unless the installed record's source_commit_verification proves tag/build
linkage. Do not substitute a source checkout, staged build, or successful
rebuild for published-asset identity.
Run one explicit case
Copy the repository into the disposable guest without a shared host mount, install the marker, and use a new evidence directory:
SCENARIO=apt-trixie
IDENTITY_FILE=/root/facelock-walkthrough-identity.json
EVIDENCE_DIR=/root/facelock-evidence/apt-trixie
python3 test/docs-walkthrough/run.py run --scenario "$SCENARIO" --identity "$IDENTITY_FILE" --output "$EVIDENCE_DIR"
python3 test/docs-walkthrough/evidence.py validate "$EVIDENCE_DIR/evidence.json"
Use --require-pass only when a passing outcome is required. A real
publication absence is evidence, not a reason to rewrite the record as a pass.
A clean apt-trixie run against an unpublished suite records the 404 its
Release URL returned; that record is the outcome, not a failed run to retry.
When an environmental prerequisite is deliberately unavailable, record an explicit blocked result rather than skipping silently. The generic runner always rejects camera and TPM devices; intentional hardware work uses the separate manual protocol.
SCENARIO=physical-tpm
IDENTITY_FILE=/root/facelock-walkthrough-identity.json
EVIDENCE_DIR=/root/facelock-evidence/physical-tpm
python3 test/docs-walkthrough/run.py blocked --scenario "$SCENARIO" --identity "$IDENTITY_FILE" --reason "no dedicated TPM passthrough guest available" --output "$EVIDENCE_DIR"
Rootless container launcher
Container-eligible cases can use the guarded launcher with the exact image from the release matrix. It creates a named, UUID-scoped, rootless container with no mounts; it is not a substitute for booted systemd/PAM evidence:
SCENARIO=deb-trixie-direct
IDENTITY_FILE=/tmp/facelock-walkthrough-identity.json
CONTAINER_IMAGE=debian:13@sha256:full-image-digest
EVIDENCE_DIR=/tmp/facelock-evidence/deb-trixie-direct
python3 test/docs-walkthrough/run.py launch-container --scenario "$SCENARIO" --identity "$IDENTITY_FILE" --image "$CONTAINER_IMAGE" --output "$EVIDENCE_DIR"
Aggregate evidence
Validate a collection and use the strict aggregate only when intentionally requiring complete execution coverage of this walkthrough catalog:
EVIDENCE_ROOT=/root/facelock-evidence
python3 test/docs-walkthrough/evidence.py aggregate "$EVIDENCE_ROOT"
python3 test/docs-walkthrough/evidence.py aggregate --require-pass "$EVIDENCE_ROOT"
Aggregation reports missing cases and unmapped documentation inventory rows.
It does not convert manual-only commands into executed coverage or let one
distribution/channel stand in for another. This optional aggregate is not wired
into just release-preflight and is not the documentation-accuracy gate.
Manual evidence
manual-sections.json presents remaining manual commands as ordered steps,
including the exact documentation text, source location, and source hash.
Manual review is not a shortcut around that binding. A passing manual record
can qualify for completion only after its checked-in case has
review_status: reviewed, concrete per-step invocation/exit/output/state
expectations, and explicit fixture bindings. Refreshing the generated catalog
resets these definitions to candidates requiring another review. The record
must include manual_review with the operator, notes,
expectations_reviewed: true, and the fixture bindings used. Each passing step
must preserve the exact documented_command, record the actual argv, expected
exit, actual output and observed state, and reference a sanitized, hashed log.
If a required expectation cannot be observed, record the case as blocked or
failed rather than marking the section complete.