Sprint 4 Design Pack — GWP-114
Story narrative
Section titled “Story narrative”The task as filed (2026-04-15) cites ADR-0003 as the spec source. ADR-0003 is archived — it was retired 2026-04-21 when the Pico-as-primary target was dropped, and the canonical update spec is now ADR-0011 (Pi Zero 2 W system image update) with ADR-0020 as the umbrella (multi-surface update model). The task body’s specifics — --simulate-update path/to/firmware.kn86fw flag, deck-state file at ~/.kn86/deck_state.bin, “validation pipeline and migration chain” — were drafted against the archived spec.
Reading ADR-0011’s Emulator Parity section carefully (L342–L351 of docs/adr/ADR-0011-device-firmware-update-system.md):
The desktop emulator is a separate compile target (SDL2 on macOS/Linux, 80x25 amber-on-black). It does not simulate: [USB-MSC, A/B slot rotation, tryboot rollback, runtime daemon]. These are hardware-target concerns. The emulator’s update story stops at
.kn86fwheader parsing: the same Rustkn86-fw-formatcrate that the flasher uses (see GWP-144) can be dropped into the emulator’s tooling so cart authors or QA can validate.kn86fwpayload structure from the dev loop without needing a Pi on the bench.For the KN-86, emulator parity is header-only: the update runtime itself is a hardware-target concern and is not mirrored in the emulator.
So ADR-0011 explicitly says emulator parity is header-only. The task as originally filed wants a “validation pipeline and migration chain” simulation that ADR-0011 has affirmatively scoped out of the emulator. Two paths:
- Realign GWP-114 to header-validation-only — add an emulator-side
--validate-fw <path.kn86fw>(or--simulate-update) flag that uses the GWP-144kn86-fw-formatcrate (or its C-side equivalent) to parse the header, verify the SHA-256 integrity field, and reportOK/FAILwith diagnostic detail. This is what ADR-0011 sanctions. Recommended. - Reframe GWP-114 entirely — declare the original 2026-04-15 framing obsolete (it predates ADR-0011’s “header-only” scoping) and re-file the work that’s actually needed (which may just be the GWP-144 crate integration, or may include a deck-state migration test harness — see below). Less efficient, same destination.
There’s also a deck-state-migration angle the original task body raised that’s worth preserving: the emulator stores deck state at ~/.kn86/deck_state.bin (or wherever the platform layer puts it on macOS/Linux), and as the deck-state schema evolves (ADR-0011 hints at migration needs; the actual schema is owned by ADR-0015’s deck-state schema additions and docs/software/runtime/deck-state.md), there should be a way to test “v1 deck state file → v2 deck state file” migrations on the emulator without flashing real hardware. That’s a valuable test surface, but it’s not coupled to .kn86fw validation — it’s a separate concern that the original task conflated. Recommend splitting:
- GWP-114-A:
--validate-fw <path>emulator flag for header parsing per ADR-0011 emulator-parity scope. - GWP-114-B (new sibling): deck-state migration test harness — invoked via Lisp REPL or test fixture, not a CLI flag — exercises
migrate_v1_to_v2()style logic. Out of scope for this sprint; flag for later.
This pack assumes the realign-to-header-validation path. If Josh prefers full reframe-and-rescope, the engineering work is similar; only the Notion-task storytelling differs.
Acceptance criteria expanded (≥4 testable items with file paths)
Section titled “Acceptance criteria expanded (≥4 testable items with file paths)”Realigned to ADR-0011’s emulator-parity scope:
kn86-emulator/src/cli.cgains--validate-fw <path>flag (or--simulate-update <path>if Josh wants to keep the original verb). Flag mode is mutually exclusive with normal boot — emulator parses, prints result, exits with non-zero on validation failure.- Emulator gains a thin C wrapper around the GWP-144
tools/kn86fw/header format. Two implementation paths:- (a) Direct C header reuse: include
tools/kn86fw/format/kn86fw.hfrom the emulator (header is already C-compatible per GWP-144’s “canonical source of truth” framing in ADR-0011 L62), call its parse + verify functions. Cleanest, no FFI. - (b) Rust crate FFI: if the canonical
kn86-fw-formatis Rust-only and not C-callable, add a minimal C++-shim or a Rust cbindgen-generated header. More work; only justify if (a) is blocked. Recommend (a) and confirm during implementation thatkn86fw.his plain C.
- (a) Direct C header reuse: include
kn86-emulator/tests/test_validate_fw.ccovers:- Known-good
.kn86fwheader parses + verifies → exit 0. - Truncated header → exit non-zero with “header too short” diagnostic.
- Bad magic bytes → exit non-zero with “magic mismatch” diagnostic.
- SHA-256 mismatch → exit non-zero with “integrity check failed” diagnostic.
- Header valid but schema version unsupported → exit non-zero with “unsupported version” diagnostic.
- Known-good
- Stdout output format machine-parseable:
- On success:
OK schema_version=N sha256=<hex> payload_size=<bytes>then exit 0. - On failure:
FAIL <error_token> <human_message>then exit 1. - Token examples:
MAGIC_MISMATCH,TRUNCATED_HEADER,SHA256_MISMATCH,UNSUPPORTED_VERSION. Tokens are stable contract for downstream tooling (CI scripts, Tauri flasher diagnostics).
- On success:
- Cross-reference and dependency note: dependency on GWP-144 (image builder owns the canonical header format). If GWP-144 is not yet in a state that exports a usable C header, this task waits. As of 2026-04-27 GWP-144 is “in progress” per ADR-0011 L297 — verify status before dispatch.
- Doc update:
docs/adr/ADR-0011-device-firmware-update-system.mdEmulator Parity section gains a one-line cross-reference to GWP-114 (Emulator-side header validation: GWP-114 ships --validate-fw flag). Or alternatively, add the same cross-reference to a new subsection indocs/device/os/release-setup.md. Light doc touch. - No deck-state migration logic in this task. Original task body’s “deck state file read/write matching on-device binary format” and “Migration scripts executed in Fe sandbox” criteria are out of scope under the realigned framing. File those as GWP-114-B sibling for later.
Edge cases (≥2)
Section titled “Edge cases (≥2)”- GWP-144 not yet exporting a C-callable interface. If GWP-144 is Rust-only with no C bindings shipped, the cleanest path is (a) blocked. Two fallbacks: (i) write a tiny C-side header parser that reads the same bytes (acceptable because the format spec is owned by GWP-144 and we’re a downstream consumer; just keep this in sync via comment + TODO referencing GWP-144 commit hash); (ii) push GWP-144 to expose a C-callable surface as a precondition for this task. Path (i) is faster but creates a sync hazard. Recommend (ii) and treat GWP-114 as blocked-on-GWP-144 until that surface lands.
- Header format evolves between emulator build and flasher version. If a cart author runs
--validate-fwon a.kn86fwproduced by a newer flasher, the emulator should fail clean withUNSUPPORTED_VERSION(criterion #3 covers this) rather than crash. Header parser must be defensive about version field. .kn86fwfile is several MB (gzipped slot image). SHA-256 verification reads the entire file. On macOS / Linux this is fast (~100 MB/s on commodity hardware) but worth knowing. Don’t load the whole file into memory; stream-hash it. The reference SHA-256 implementation in any std-library should handle this trivially.~/.kn86/deck_state.binreference in original task body. Confirm where the emulator currently writes deck state —docs/software/runtime/deck-state.mdshould have the canonical path. If the path drifts post-ADR-0019 (which moved per-cart save to SD filesystem; Universal Deck State stays on-device per ADR-0011), document the current emulator path in the design pack body but DON’T touch that surface in this PR.
Engineering hand-off notes
Section titled “Engineering hand-off notes”- Files owned:
kn86-emulator/src/cli.c(additive — new flag handler), maybekn86-emulator/src/firmware_validate.c(new file for the wrapper) if (a) is non-trivial. - Files added-to:
kn86-emulator/tests/test_validate_fw.c(new),kn86-emulator/CMakeLists.txt(link tools/kn86fw/format if needed),docs/adr/ADR-0011-device-firmware-update-system.md(one-line cross-reference) ORdocs/device/os/release-setup.md. - Files NOT touched: any deck-state code, any cart code, any nOSh runtime path. This is a pre-boot CLI flag, not a runtime feature.
- Expected PR size: ~50 lines cli.c, ~80 lines firmware_validate.c (if needed), ~120 lines tests. Single C engineer, ~half a day with TDD.
- Test strategy: TDD. Author 5 test cases (known-good + 4 failure modes) using fixture
.kn86fwfiles generated bytools/kn86fw/at test-runtime (or pre-generated and committed underkn86-emulator/tests/fixtures/). Implement to pass. - Dispatch shape: single C engineer, additive. BLOCKED on GWP-144 status check — verify GWP-144 has shipped a usable header parser surface (C-callable preferred) before dispatching this. Light coordination required.
- Watch for: the ADR-0011 emulator-parity language is the load-bearing constraint. Don’t let the implementing agent expand scope into “simulating the runtime daemon” or “A/B slot rotation in the emulator” — those are explicitly scoped out.
Open questions for Josh
Section titled “Open questions for Josh”- Confirm scope realign to header-only. ADR-0011 says emulator parity is header-only; original 2026-04-15 task body wants migration-chain simulation that ADR-0011 scopes out. Recommendation: realign to header-only, file the migration-test-harness piece as a separate sibling task (GWP-114-B) for later. Confirm.
- Flag name preference.
--validate-fw <path>(more accurate to the realigned scope) vs--simulate-update <path>(preserves original task verb but slightly misleading). Recommendation:--validate-fw. Either works — implementer’s call if Josh has no preference. - GWP-144 status check. Is the
tools/kn86fw/header parser shipped + C-callable? If yes, dispatch GWP-114. If no, GWP-114 is blocked — and that’s a planning data point. - Deck-state migration test harness as sibling task? Recommend filing GWP-114-B for the deferred concern (test fixture for
migrate_v1_to_v2()style logic, invoked via Lisp REPL orctest). Confirm whether to file now or defer until v0.1 deck-state schema actually starts evolving.