The active platform is Debian 13 Trixie / amd64, using
vfd-lantern-podman-01 (Podman image r3). Arm64 and other operating systems
remain at the end of the roadmap, after the Debian 13 amd64 qualification.
See platform policy.
Issue #21 supplies infrastructure and mock demonstrations. Issue #27 supplies additional functional scenarios; #25 qualifies the actual candidate, including 24-hour soak, physical HIL and performance budgets. A mock report must never be used as candidate evidence. #21 does not require a draft release or real VFD.
ci.yml calls ci-quality.yml with the exact commit. Only the existing self-hosted
Debian runner is eligible; its name, OS and architecture are checked. Fork PRs do
not execute automatically on the persistent runner. Maintainers review their code
before explicitly dispatching the reusable quality gate for its immutable commit.
No checkout credentials persist. Actions use full commit SHAs and read-only
permissions.
scripts/ci/Containerfile starts from the Debian Trixie image digest recorded in
tools.lock.toml. APT installs rustup and the build dependencies. The host prepares
the pinned Rust 1.97.1 toolchain, exact Cargo tools and dependency downloads; build
and test execute inside that image with no network, 4 CPUs, 6 GiB RAM and a
1024 PID limit. Toolchains mount read-only. Cargo objects persist outside the
checkout under $HOME/.cache/vfd-lantern-ci-target/<containerfile-hash>; source and
compiler fingerprints control reuse. Reports are cleared before every execution,
and coverage starts with a workspace clean. Only fresh evidence is copied back
for upload. This cache is separate from protected long-run staging. Container execution uses the working
Podman configuration established for #24; it does not write /proc/sys.
The quality gate performs fmt, Clippy with warnings denied, legal feature checks, doctests, rustdoc with warnings denied, architecture checks, Criterion smoke and the existing telemetry/Scope/parameter performance checks. Nextest discovers all workspace tests, including new #27 tests, and separates fast simulator unit tests, serial PTY/transport tests and filesystem fault tests. JUnit is retained.
Coverage includes the workspace tests and process-level E2E, with both product and
simulator binaries instrumented. The threshold is 80% of lines globally; no
production file is excluded to meet it. Instrumented CLI contracts also verify
normalization/hash stability, approval rejection/creation in isolated XDG paths,
and backup tamper rejection. Acceptance exposed and fixes two product regressions:
CSV data-channel EOF no longer terminates its writer before durable finalization,
and unchanged parameter visibility updates preserve operator-visible write refusals.
The CSV regression checks completed metadata and removal of the runtime checkpoint.
The PTY harness exercises monitoring, completed CSV
and its sidecar, fault acknowledgment/export integrity, and read-only write guards.
The quality gate additionally passes --write-fixture: this launches a disposable
PTY simulator, approves its profile only in temporary XDG directories, and checks
in a complete trace from a separate process that an incorrect confirmation sends
no write. Exact confirmation sends one FC06 to the read-only #20 simulator, which
returns IllegalFunction. The product must report DeviceRejected, perform zero
retries and durably audit that exact outcome. Verified write/read-back is covered
by the kernel and restore contracts; this mock does not claim a successful device
write. Long-run mock demonstrations do not pass this option. Restore unit contracts cover single-use permits, audit order,
abort, changed preconditions and failed read-back. HTML and JSON reports are uploaded, also
on failure. Critical write, trust, audit, restore and state rules retain their
positive and negative tests irrespective of the aggregate percentage.
The supply-chain stage executes real machete, deny, audit and cargo vet check.
The report distinguishes audited/imported/exempted/uncovered entries. Criterion
adds development-only safe-to-run exemptions, explicitly not audits. Existing
runtime dependency versions remain pinned. Cargo builds/tests consume committed
lockfiles; formatters and tool commands without a --locked option are exceptions.
ci-nightly.yml is reusable, scheduled and manually dispatchable. The pinned
nightly-2026-09-06 has Rust source and Miri available for amd64. Miri checks the
pure domain and profile hash tests with isolation enabled. Only Miri disables
Proptest's filesystem failure persistence; fixed inputs enter the isolated program
through -Zmiri-env-set. Its seed and results are retained
in the Miri logs, while native tests keep normal persistence. Five libFuzzer targets cover profile parsers,
canonical round trips, address/function bounds, register codecs and persistent
backup decoding. Seeds, logs and crash artifacts are retained. The fuzz workspace
has its own committed lockfile and pins libfuzzer-sys exactly. cargo-fuzz 0.13.1
does not forward --locked; a scoped Cargo launcher adds it to every child
build/metadata command and delegates to the exact pinned nightly Cargo binary.
Unknown child commands fail closed. The launcher preserves arguments and exit
status; it does not modify the installed tool or generate a replacement lockfile.
The previous unused cargo-mutants = 26.0.1 pin does not exist in the crates.io
index. #21 pins the published 27.1.0 release, which supports TOML 1.1. Cargo
commands launched by it receive --locked through --cargo-arg=--locked.
The mutation demonstration inventories the domain mutation space and exercises
ModbusFunction::validate_count; it is a bounded demonstration, not a claim that all mutations
were killed. Its actual outcomes are retained. Broader campaigns and budgets are
selected during #25. Criterion has nine benchmark families: codec, planner,
downsampling, pipeline lifecycle, profile validation, semantic diff, render model,
JCS/SHA-256 and durable AuditPort decisions.
ci-long-run.yml serves soak, hil and performance. Inputs are:
| Input | Meaning |
|---|---|
commit |
Exact 40-character source commit |
artifact-name, artifact-run-id |
Artifact from a workflow run in this repository |
artifact-sha256 |
SHA-256 of gate-bundle.tar inside that artifact |
profile-path, profile-sha256 |
Checkout-relative profile path and byte digest |
scenario-path, scenario-sha256 |
Checkout-relative scenario path and byte digest |
duration-seconds |
Positive duration, default 86400, maximum 172800 |
output-schema-version |
Currently 1 |
seed |
Explicit unsigned decimal seed |
demo |
Default false; true permits only mock evidence on the feature runner |
write-enabled |
Default false; physical HIL writes require protected hil-write |
Profile/scenario SHA values in this transport contract describe file bytes.
Candidate HIL reports must additionally identify the canonical validated profile,
firmware, hardware fingerprint, adapter, product digest and audited write outcome.
The gate returns its uploaded manifest name and pass/fail status.
A bundle contains known members gate-driver, vfd-lantern, lantern-sim,
connection_process_acceptance and infrastructure. Extraction copies only these
names into a private temporary directory and does not trust archive permissions
or paths. gate-driver REPORT_PATH receives the validated contract as environment
variables and the extracted binary directory as GATE_BINARY_DIR. A candidate
producer must consume the requested scenario and duration, enforce read-only mode
unless explicitly enabled, and report measured CPU, peak RSS, latency and drops.
The mock producer executes bundled PTY process acceptance or Criterion smoke;
its latency refers to the complete iteration, not Modbus frame latency.
The first job downloads and hashes the artifact before starting the producer. The producer receives no GitHub/upload token. It can run for 24 hours without an API call. Evidence lives under:
<staging-root>/<runner>/<workflow-run-id>/<artifact-sha>/<gate>-<attempt>/
The completion marker binds runner, run ID, attempt, commit, artifact/profile/
scenario hashes, seed, gate, exit code and hashes of both report and log. It is
written last by atomic rename. Missing markers, symlinks, additional files,
identity mismatches and digest mismatches are rejected. Per-workflow concurrency
and a local flock exclude overlapping producers/verifiers on a runner.
The dependent upload job starts with a fresh token, checks it is on the same
runner, verifies the marker and uploads the report. Cleanup happens only after a
successful upload and successful gate. Failed runs retain evidence for diagnosis;
staging belonging to another run or producer attempt is never reused. An upload-only
retry uses the original producer attempt from needs.gate-run.outputs, allowing a
new upload token without repeating the 24-hour test. Cancellation before
the marker leaves incomplete evidence that cannot be accepted.
scripts/ci/test-staging.sh tests tampering, wrong hashes/run/attempt/commit/seed,
missing completion, replay, symlinks, mutual exclusion, token stripping and
cleanup before/after upload confirmation.
These are production requirements, not prerequisites for the #21 mock demo:
- Unique soak label
vfd-lantern-soak-amd64, HIL labelvfd-lantern-hil, and a stable performance labelvfd-lantern-perf-amd64on Debian 13 amd64 runners. - Each production runner also has a unique label equal to its runner name,
so the upload job returns to the exact machine. Ordinary
podman/vfd-lanternfeature labels do not substitute for production long-run labels. - Persistent
/var/lib/vfd-lantern-gates, owned by the runner account, mode 0700, survives job cleanup and container restart. Repository variableVFD_LANTERN_STAGING_ROOTcan select another absolute path with the same rules. Do not prune incomplete/failed evidence automatically. - A protected
hil-writeEnvironment requires reviewer approval before writes; physical adapters, device allowlists and hardware metadata are supplied for #25. Default operation remains read-only. The mock demo requires no device access.
The demo uses $HOME/.local/state/vfd-lantern-gates on the existing feature runner,
separate run/upload jobs, and a short duration. issue21-acceptance.yml supports
manual dispatch, builds its nonproduction bundle and exercises each gate.
A newer demonstration commit cancels the obsolete acceptance run so the sole
feature runner tests the current version; incomplete staging remains ineligible
for acceptance. Its reports are always marked
evidence_kind: mock. Green mock runs do not constitute 24-hour or physical HIL
qualification.