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
Preparing the repo to go public (ADR-0007):
- CI: kernel-build moves from the self-hosted runner to ubuntu-latest
(installs its own cross toolchain + qemu, caches the pristine tarball).
On a public repo a registered self-hosted runner is reachable from
approved fork-PR workflows — i.e. arbitrary code on private
infrastructure — and the build never actually needed the SDK host.
ADR-0004 marked superseded-in-part; docs/ci-cd.md rewritten (site
specifics now live only in the private deployment log).
- Licensing: LICENSE gains the GPL-2.0 carve-out for patches/ and the
kernel source excerpts (Linux derivatives; per-driver provenance was
already tracked in PROVENANCE.md); patches/README.md states it too.
- Scrubbed from the tip: bench-unit dev credentials and its gadget IP
(m2-boot notes), the site AP SSID+BSSID and a neighboring AP's BSSID
and the device WLAN MAC (wifi bring-up evidence — BSSIDs are
geolocatable), the runner mesh IP. NOTE: these remain in git history;
decision on a pre-publication history rewrite is separate.
- Emoji cleanup across 21 tracked files (kernel port docs, review report,
enforce-mcdc.sh) per repo text conventions: status marks became
[x]/[wip]/[ ]/OK plain text.
- "[maintainer]-gated" process phrasing normalized to "maintainer-gated"
(attributions in dated evidence docs kept).
Verified: zero emojis tracked; scrub grep clean; patches carry no internal
references; ci.yml parses; shellcheck unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
Phase 4+5 of the device sim:
- Display + touch verified end-to-end: virtio-gpu at 720x720 (fbdev
emulation) renders the real WardenOS dashboard from the static LVGL
fbdev+evdev UI build (flare-edge qemu-vm-support tools/build-ui-vm.sh);
QMP input-send-event taps the Metrics tab and qemu/tests/ui-shot.sh
asserts the repaint from screendumps. Two load-bearing QEMU flags found
and documented: -global virtio-mmio.force-legacy=false (gpu/input are
VERSION_1-only) and the 200ms press hold (an instantaneous press+release
lands inside one LVGL indev poll and never clicks).
- qemu/tests/qmp.py: minimal QMP client (screendump, tap, quit).
- stage-2 init starts warden-ui when present and fb0 exists.
- docs/decisions/0006-qemu-device-sim.md: virt-not-custom-board, the
enters-at-kernel boundary, fragment policy, naming, consequences.
- docs/architecture.md: new section 7 (device emulation), order-of-work
item 7; modbus cross-reference to the bridge.
- qemu/README.md: emulated-vs-not table, scenarios, gotchas, host/runner
requirements. docs/ci-cd.md: runner needs one-time qemu-system-arm
install (fail-closed smoke until then, [maintainer]-gated). Repo README updated.
Final sweep on this commit: shellcheck clean, bridge 7/7 tests, boot smoke
PASS, portal scenario PASS (check-in + fw pull + signed .wfw download),
ui-shot PASS (touch navigates to Metrics) — all under the final flags.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
The third simulator (deliberately not named "sim"): a QEMU -M virt VM that
boots the real 6.18.46 kernel and enters at -kernel zImage — everything below
(BootROM/idblock/U-Boot/real BCB A/B selection) is closed blobs + mask ROM
and is explicitly out of scope.
- qemu/mkinitramfs.sh: pinned static busybox (sha256 fail-closed) + rootfs/
- qemu/mkimage.sh: unprivileged sparse disk image with the device's canonical
12-partition blkdevparts A/B layout (vda == mmcblk0 mapping)
- qemu/rootfs/: stage-1 init (by-name symlinks from PARTNAME uevents,
whole-token warden.slot= parse, switch_root) + stage-2 init (userdata/oem
mounts, slirp networking, payload daemon start)
- qemu/run.sh: runner with --slot/--rtc/--watchdog/--rs485/--qmp/--display
- qemu/configs/virt.fragment + WARDEN_KCONFIG_FRAGMENT hook in
build/build-kernel.sh (canonical RV1106 build untouched when unset):
adds PCI, pci-serial, i6300esb watchdog, WireGuard, virtio-gpu/input
- qemu/tests/boot-smoke.sh: sentinel-asserting boot test
Verified on QEMU 10.0.11: canonical zImage boots -M virt unmodified (the
feared DEBUG_UNCOMPRESS decompressor hang does not exist in 6.18); full
stack boots both slots; 12 by-name symlinks; userdata persists across
reboot; -rtc base=2021-01-01 reproduces the no-RTC wrong-clock class.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K