9e28defcab
int_max_keys() computed an internal B+tree node's key capacity as IDX_BODY/8 - 1 (2041 at a 16 KB page), dividing by 8 and ignoring that each key also carries an 8-byte child pointer. The true capacity is (IDX_BODY-8)/16 = 1020. An internal node was therefore allowed to grow to ~2x what a page holds; once it crossed 1020 keys, btree_insert's write-back overran its STORE_PAGE_SIZE stack page buffer and smashed the stack canary (__stack_chk_fail / SIGABRT). A clean/small store never grows an internal node that large, so it never tripped; the ~8x-bloated live store (a day of tombstone churn) plus a 44 MB un-checkpointed WAL replayed on open pushed a node over the boundary during redo -> deterministic crash loop (btree_insert <- apply_edge_put <- engram_open <- engram_store_boot). Fixes: - int_max_keys: use (IDX_BODY-8)/16 so internal nodes split at the real page capacity. - btree_insert: reject any page whose on-disk nkeys exceeds physical capacity (fail loud, never smash the stack) -- overflow is now impossible regardless of on-disk content. - read_body: bound the slot (off,len) and record length to the page before dereferencing; a stale/torn index entry could otherwise make store_get_node read off the stack (observed EXC_BAD_ACCESS on the bloated store). Fail safe. Verified on a COPY of the live store: unfixed binary SIGABRTs in btree_insert on open; fixed binary boots clean, recovers the store, checkpoints the WAL, and M5 compaction shrinks 458 MB -> 57.7 MB with node/edge counts preserved.