Skip to content

fix(firmware): populate bundled bootloader hash table#112

Merged
BitHighlander merged 1 commit into
developfrom
fix/bundled-bootloader-hashes
Apr 18, 2026
Merged

fix(firmware): populate bundled bootloader hash table#112
BitHighlander merged 1 commit into
developfrom
fix/bundled-bootloader-hashes

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

Summary

Fixes the likely root cause of the "unknown bootloader" ticket: the OOB wizard installing firmware on a device whose bootloader needs updating first.

firmware-bundle/releases.json shipped with hashes.bootloader: {}. When the remote manifest fetch at startup fails (offline, upstream outage, DNS), the merged manifest has no bootloader hash → version entries. For devices in firmware mode where features.bootloaderVersion is absent, resolution falls back to the hash table — with an empty table, effectiveBlVersion stays undefined and needsBootloaderUpdate collapses to false. The OOB wizard then skips the bootloader step and installs firmware on top of the old bootloader, which surfaces as "unknown bootloader" on next boot.

This PR mirrors the remote table (v1.0.0 → v2.1.4) into the bundle so bootloader detection works offline and in degraded-network conditions.

Test plan

  • With network disabled at app launch, plug in a KeepKey running firmware but with an older bootloader → OOB wizard detects needsBootloaderUpdate=true and routes to bootloader step (not firmware).
  • With network enabled, confirm no behavior change — remote entries merge cleanly over bundled ones.
  • Confirm [Engine] Loaded bundled manifest log shows latest=v7.10.0 beta=v7.14.0 as before.
  • Regression: normal OOB flow on a fresh device still progresses welcome → bootloader → firmware → init.

Bundled releases.json shipped with hashes.bootloader={}. When the remote
manifest fetch fails (offline, CI outage, DNS), the merged manifest has
no bootloader-hash -> version entries. For devices in firmware mode that
don't report features.bootloaderVersion, resolution falls back to the
hash table; with an empty table, effectiveBlVersion stays undefined and
needsBootloaderUpdate collapses to false. The OOB wizard then skips the
bootloader step and installs firmware on the old bootloader, surfacing
as 'unknown bootloader' on next boot.

Mirror the remote table (v1.0.0 through v2.1.4) into the bundle so
bootloader detection works offline and in degraded-network conditions.
@BitHighlander
BitHighlander merged commit 1ab605d into develop Apr 18, 2026
3 checks passed
BitHighlander added a commit that referenced this pull request Jul 16, 2026
…t through (#365)

* feat(mcp): make /mcp a dumb pipe — serve the BEX catalog, pass content through

The vault hardcoded the MCP tool catalog and text-wrapped every tool result.
Two consequences: each new BEX tool needed a cross-repo change (tools/list
never mentioned it, tools/call rejected it at the TOOL_NAMES guard), and
bex_screenshot was impossible — MCP returns images as {type:'image'} content
blocks and the vault could only emit text.

The nine browser-driving tools are merged and shipping in keepkey-client
(#112/#113) and are inert until this lands.

- tools/list proxies to bex_list_tools; TOOLS becomes FALLBACK_TOOLS, served
  only when the bridge is down so `claude mcp add` still succeeds and the agent
  can reach bex_status to find out why.
- tools/call passes an MCP content-block result through untouched; a plain
  result is still text-wrapped as before.
- Drop the TOOL_NAMES guard. The vault can no longer know the valid names and
  doesn't need to: the BEX's unknown_tool surfaces as an isError result via the
  existing catch. One rejection path, in the process that owns the catalog.

Auth is untouched: /mcp stays bearer-authed, loopback-only, browser-excluded,
which matters more now that bex_click can drive a logged-in tab. The BEX-side
Agent-mode toggle (default off) remains the gate.

Tests drive a fake BEX socket, so the bridge-up paths (proxied catalog, image
passthrough, unknown_tool) are covered with no extension and no device. The
four new assertions fail against the unpatched handler.

Implements HANDOFF_vault_mcp_dumb_pipe.md (keepkey-client#114).

* fix(mcp): validate the tool-name shape, and keep unknown_tool a protocol error

Review follow-ups on the dumb-pipe change.

1. Dropping the TOOL_NAMES guard also dropped the only check that `name` was a
   usable string. The BEX drops frames with a non-truthy `tool` WITHOUT replying
   (mcpBridge.ts `if (!msg?.id || !msg?.tool) return`), so a missing/empty name
   was forwarded and then sat for the full 30s CALL_TIMEOUT_MS — where it used
   to fail instantly with -32602. Validate the shape before forwarding: the
   vault owns the request shape, the BEX still owns which names are real.

2. The MCP tools spec treats an unknown tool as a PROTOCOL error (-32602);
   isError is for a valid tool that failed while executing. Map the BEX's
   unknown_tool back onto rpcError instead of dressing it up as a successful
   result. The BEX still decides — we only translate its verdict into the
   envelope the spec asks for. No hardcoded catalog comes back.

The fake BEX in the tests now mirrors the real one's silent-drop behavior, so
the bridge-up regression test genuinely reproduces the hang: with the guard
removed it times out instead of asserting.

* chore(mcp): drop the unused post() test helper

Dead since before this PR: post() builds a Request and returns it, but nothing
calls it — every test goes through call(), which does the same construction and
then actually invokes the handler. tsc flagged it TS6133.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant