Silence the shipping targets, and keep a crash record that carries no secrets
A _release build was not silent. CONFIG_LOG_MAXIMUM_LEVEL=0 compiles out the
ESP_LOG family, but the panic handler never goes through the log subsystem at
all: panic_print_char writes straight into the UART or USB-Serial/JTAG TX FIFO,
and esp_backtrace_print reaches esp_rom_printf with no log guard, so a Task-WDT
timeout printed a full backtrace on a shipped device.
A new sdkconfig.silent.defaults sets the five options that actually close every
path and is chained last on every _release and _secure env. That required
declaring a fragment chain on the _release envs, which had none: despite its
name, sdkconfig.release.defaults.opt-in is reached only by _secure and
_secure_dev, verified by resolving the extends chains rather than by reading the
file names. Invariant 18 re-derives the chain per environment and fails if any
shipping target does not resolve to all five symbols.
Silence would leave a field crash with nothing behind it, so the firmware now
keeps its own record. A --wrap hook on esp_panic_handler — ESP-IDF's own
mechanism, used the same way inside components/bt — captures the reset reason and
up to twelve PC/SP pairs of the faulting task into an RTC no-init ring, then
calls the real handler unchanged. Addresses only: no stack contents, no
registers carrying data, no heap. That distinction is the whole point, because a
task stack on this firmware holds the PIN across the PBKDF2 window and the HMAC
pads that are key-equivalent, and .bss holds the master key.
RTC memory survives a panic reboot but not a power cycle, and NVS cannot be
written from a panic context, so the ring is captured to RTC and mirrored to NVS
on the next normal boot, writing flash only when the record set actually changed.
It is surfaced at GET /api/system/crash behind the portal's existing session
auth, with counters on the polled /api/system, and CRASH? / CRASHCLEAR on debug
builds.
One clause of the ruling is not met and cannot be: the mask-ROM banner prints
before any firmware exists. ESP-IDF says so itself in the ESP_CONSOLE_NONE help.
The only lever is an eFuse, it does not exist at all on the four classic boards,
and burning it on the S3 would contradict the rule that only _secure touches
eFuses. Documented rather than worked around.
Verified: sticks3_release, m5stickc_plus2_release and sticks3_secure all build
clean, the generated sdkconfigs carry all five symbols on the S3 and the four
that exist on classic, native tests pass 2861/2861, and both guards are green.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r
Raise the per-variant capacities, and stop shipping unusable vault UI
Every board now pins the capacity its flash and RAM actually support:
tdeck and cores3_se 700, core2_v13 500, sticks3 and cardputer 250,
m5stickc_plus2 180. The two 4 MB boards stay at 100 — their rekey-safe
ceiling is 139, and staging a second vault for a change-PIN on a fuller
one would run the filesystem out of space.
m5stickc_plus2 could not honestly hold 180: it had 3,132 B of internal
DRAM left against the 4,096 B bar. The slack came from a real absurdity.
Every button board compiled BOTH vault-menu layouts and picked one at
runtime from whether a third button existed, though a board's buttons are
fixed at manufacture. Each unused layout carries a row array sized by
capacity, so plus2 was paying 5 KB for a screen it can never draw. Boards
now declare which layout they use and only that one is compiled: plus2
goes to 9,196 B, sticks3 to 143,141, cores3_se to 78,985. core2_v13
recovers only the view object because its row arrays already live in
PSRAM, which is the placement working as intended.
The declaration is deliberate rather than inferred. Button count looked
like the discriminator only because today's fleet correlates: cardputer
and tdeck are wide-screen boards with no third button, and the old rule
would have handed them the narrow layout if their keyboards ever went
away. What actually decides the layout is width — a sliding pill
indicator is what fits 135 px, while a top tab bar plus an action bar
need room, and a past CoreInk header garble came from that view
hardcoding 320x240. Input decides only how you move between tabs. So the
variant states its choice, with no default, and a build-time tie rejects
a board declaring a layout its hardware cannot drive.
The names were lying too. Both layouts draw tabs, so "Tabbed"
distinguished nothing: they are now NarrowVaultView and WideVaultView,
after the constraint that actually separates them. KeyboardVaultView
keeps its name on purpose — cardputer is 240x135 and tdeck is 320x240,
one narrow and one wide, and both use it, so there the discriminator
really is the input device.
Review of the change caught a T-Deck pin that had never been raised while
five other artifacts already advertised 700, a NAV TOTP macro that
regressed on the wide boards so the screenshot suite filed a vault-list
capture as the TOTP screen, a selector whose "no silent default" promise
had a hole (an unknown token preprocesses to zero, which was the one
value exempted, so a typo compiled no view at all), and a variant guard
whose regex rejected correct declarations that carried a trailing
comment.
Verified: native 2861/2861, all three repo guards, and all eight boards
build with their declared layout confirmed present and its siblings
absent in each map.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r