Skip to content

Sprint 4 Design Pack — GWP-114

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 .kn86fw header parsing: the same Rust kn86-fw-format crate that the flasher uses (see GWP-144) can be dropped into the emulator’s tooling so cart authors or QA can validate .kn86fw payload 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:

  1. Realign GWP-114 to header-validation-only — add an emulator-side --validate-fw <path.kn86fw> (or --simulate-update) flag that uses the GWP-144 kn86-fw-format crate (or its C-side equivalent) to parse the header, verify the SHA-256 integrity field, and report OK/FAIL with diagnostic detail. This is what ADR-0011 sanctions. Recommended.
  2. 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:

  1. kn86-emulator/src/cli.c gains --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.
  2. 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.h from 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-format is 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 that kn86fw.h is plain C.
  3. kn86-emulator/tests/test_validate_fw.c covers:
    • Known-good .kn86fw header 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.
  4. 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).
  5. 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.
  6. Doc update: docs/adr/ADR-0011-device-firmware-update-system.md Emulator 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 in docs/device/os/release-setup.md. Light doc touch.
  7. 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.
  1. 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.
  2. Header format evolves between emulator build and flasher version. If a cart author runs --validate-fw on a .kn86fw produced by a newer flasher, the emulator should fail clean with UNSUPPORTED_VERSION (criterion #3 covers this) rather than crash. Header parser must be defensive about version field.
  3. .kn86fw file 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.
  4. ~/.kn86/deck_state.bin reference in original task body. Confirm where the emulator currently writes deck state — docs/software/runtime/deck-state.md should 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.
  • Files owned: kn86-emulator/src/cli.c (additive — new flag handler), maybe kn86-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) OR docs/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 .kn86fw files generated by tools/kn86fw/ at test-runtime (or pre-generated and committed under kn86-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.
  1. 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.
  2. 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.
  3. 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.
  4. 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 or ctest). Confirm whether to file now or defer until v0.1 deck-state schema actually starts evolving.