review: iteration-1 fixes across CI, bridge, VM harness, and docs

CI/pipeline:
- KERNEL_TARBALL passed as a YAML env literal '~' was never tilde-expanded
  and would have failed every hosted kernel-build dispatch; the path is now
  exported from the shell. Verified reproducible before the fix.
- Every job gets timeout-minutes; boot smoke uses timeout -k so a wedged
  qemu is SIGKILLed instead of holding the job.
- Tarball fetch + fail-closed sha256 verification deduplicated into
  build/fetch-kernel-tarball.sh (with curl retries), used by build-kernel.sh
  and both CI jobs. busybox fetch gains retries too.
- ccache layer for kernel-build (cache keyed on defconfig+patches) recovers
  the incremental-compile speed the ephemeral-runner move cost.
- build-kernel.sh now asserts every fragment option survived olddefconfig —
  merge_config -m pastes text and Kconfig silently drops unmet symbols.

rs485-bridge:
- pending-buffer cap (2x max RTU ADU) instead of unbounded growth;
  explicit accept-loop error handling with backoff instead of .flatten();
  per-arm inline bounds instead of the string-keyed lookup whose default
  would have mis-bounded a future get-input; control-socket cleanup errors
  surfaced; flag-shaped values rejected in arg parsing; doc example uses a
  private mktemp dir. Test timing margins widened for contended runners
  (gap 25->120ms, 60x margin on the split-frame test).

VM harness:
- stage-1/stage-2 boot scripts share one validated slot parser and one
  by-name populator (qemu/rootfs/etc/warden-lib.sh) — the duplicated
  parser had already diverged on validation; userdata/oem mount failures
  now fail fast with a greppable sentinel; udhcpc fallback keys off the
  interface actually having an address; switch_root applet guarded.
- boot-smoke delegates the qemu invocation to run.sh (machine shape lives
  in ONE place); run.sh port 0 disables a hostfwd.
- mkimage: unknown partition names fail at build time; DISK_END is a max,
  not last-entry; --state keys validated as filenames.
- portal-scenario: mock readiness is asserted (no silent fall-through),
  hostfwd port collisions retried, mount-failure sentinel fails fast.
- ui-shot: fixed sleeps replaced with bounded screendump polling; the
  repaint assertion is real and documented as such. qmp.py loses its
  module-global and gains argv validation.

Docs/scrub: bench-host paths and the site AP name removed from six more
port docs and two evidence tables; path-bearing build artifacts (.elf,
.map) untracked (the 154-byte firmware .bin is path-free and stays);
ADR-0003 marked visibility-superseded by ADR-0007; stale section
cross-reference fixed; flare-edge noted as private for outside readers;
stale root-level review report removed per the new workspace rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
This commit is contained in:
BFE Engineering
2026-08-30 08:19:17 -06:00
co-authored by Claude Fable 5
parent b667ff5b1e
commit 2756de0b46
24 changed files with 481 additions and 203 deletions
+40 -19
View File
@@ -14,7 +14,9 @@
# JOBS parallel make jobs (default: nproc)
# WARDEN_KCONFIG_FRAGMENT
# optional kconfig fragment merged onto warden_defconfig
# (qemu/configs/virt.fragment builds the QEMU -M virt variant)
# (qemu/configs/virt.fragment builds the QEMU -M virt variant);
# every fragment option is verified to have taken effect
# WARDEN_CCACHE=1 compile through ccache (CI caches ~/.ccache)
#
# Requires: `python` (not python3) on PATH — the SDK quirk; the CI runner provides
# a project-local venv. Builds are SERIAL on the shared SDK box — never run two.
@@ -24,9 +26,8 @@ KVER=6.18.46
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # build/
REPO="$(cd "$HERE/.." && pwd)"
PATCHES="$REPO/patches"
SHA_FILE="$HERE/linux-$KVER.tar.xz.sha256"
JOBS="${JOBS:-$(nproc)}"
URL="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$KVER.tar.xz"
# The tarball URL + sha256 pin live in fetch-kernel-tarball.sh (shared with CI).
# A caller-provided WORK (e.g. CI's ${{ github.workspace }}/kbuild-out, from which
# artifacts are uploaded) is left intact; a scratch dir we mktemp'd here is our own
@@ -47,21 +48,9 @@ command -v python >/dev/null || { echo "need 'python' (not python3) on PATH —
# 1. obtain + verify the pristine tarball
mkdir -p "$WORK"
TB="${KERNEL_TARBALL:-$WORK/linux-$KVER.tar.xz}"
if [ ! -f "$TB" ]; then
log "downloading $URL"
curl -fSL "$URL" -o "$TB"
fi
# Fail closed: a missing pin (e.g. forgotten on a KVER bump) or a KERNEL_TARBALL
# pointed at an arbitrary file must refuse to build, never silently skip the check
# — the pristine tarball is the ONLY external input and integrity is the whole point.
[ -f "$SHA_FILE" ] || {
echo "FATAL: no pinned sha256 for linux-$KVER (expected $SHA_FILE) — refusing to build from an unverified tarball" >&2
exit 1
}
want="$(cat "$SHA_FILE")"
got="$(sha256sum "$TB" | awk '{print $1}')"
[ "$want" = "$got" ] || { echo "tarball sha256 mismatch: want $want got $got" >&2; exit 1; }
log "tarball sha256 verified"
# Fetch + fail-closed sha256 verification live in ONE place shared with CI
# (a missing pin or a mismatch always refuses to build).
bash "$HERE/fetch-kernel-tarball.sh" "$TB"
# 2. extract pristine
SRC="$WORK/linux-$KVER"
@@ -124,9 +113,41 @@ command -v "${CROSS_COMPILE}gcc" >/dev/null \
|| { echo "cross toolchain ${CROSS_COMPILE}gcc not on PATH (set SDK_TC, or CROSS_COMPILE to one that is)" >&2; exit 1; }
make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" olddefconfig >/dev/null
# Fragment took-effect assertion: merge_config -m only pastes text, and
# olddefconfig silently resolves any symbol whose dependencies are unmet —
# a fragment option could be dropped without a word. Verify every explicit
# request in the fragment survived into the final .config; fail loud if not.
if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
frag_fail=0
while IFS= read -r line; do
case "$line" in
CONFIG_*=*)
grep -qxF "$line" "$SRC/.config" || {
echo "FATAL: fragment option '$line' did not take effect (unmet Kconfig dependency?)" >&2
frag_fail=1
} ;;
"# CONFIG_"*" is not set")
opt="${line#\# }"; opt="${opt% is not set}"
grep -qE "^$opt=" "$SRC/.config" && {
echo "FATAL: fragment disabled '$opt' but it is set in the final .config" >&2
frag_fail=1
} ;;
esac
done < "$FRAG"
[ "$frag_fail" = 0 ] || exit 1
log "fragment options verified in final .config"
fi
# Optional ccache (CI: cache ~/.ccache across dispatches; harmless if unset).
KCC="${CROSS_COMPILE}gcc"
if [ "${WARDEN_CCACHE:-0}" = 1 ]; then
command -v ccache >/dev/null || { echo "FATAL: WARDEN_CCACHE=1 but ccache not installed" >&2; exit 1; }
KCC="ccache ${CROSS_COMPILE}gcc"
fi
# 5. build zImage + the board dtb
log "building zImage + rv1106-warden.dtb (-j$JOBS)"
make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" -j"$JOBS" \
make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" CC="$KCC" -j"$JOBS" \
zImage rockchip/rv1106-warden.dtb
Z="$SRC/arch/arm/boot/zImage"