Found by the manager_routing fuzz target (docs/research/fuzzing.md \u00a77.8):
- ChannelManager::open_channel / adopt_channel used
HashMap::insert(...).is_some() as a collision check \u2014 insert REPLACES
the existing entry, so a duplicate adopt/open installed the new state,
dropped the live channel's demux_sender (spurious EOF to its readers,
subsequently routed chunks lost) and still returned
Err(ChannelExists). Fixed with contains_key pre-check; map untouched
on collision. Regression tests: adopt_channel_duplicate_id_leaves_
live_channel_intact, open_channel_duplicate_id_leaves_live_channel_
intact.
New fuzz targets (\u00a77.4 step 3):
- manager_routing: Arbitrary op sequences over ChannelManager; exact
counter models (parked/dropped must equal the manager's monotonic
counters), parked-bytes bound per \u00a76.2-1, clear_all ledger-vs-map
semantics, drainer-byte reconciliation (lossless routing)
- envelope_semantic: constructors -> serde -> write_frame/read_frame
structural round-trip; event-type constants; call.error parse-back
- spec_parse: OpRegisterRequest::from_json -> rebuild -> registry
registration (attacker schemas compile at register, CF-003)
- fuzz/shared/src/arbitrary_value.rs: bounded Arbitrary for
serde_json::Value; 20 spec_parse + 4 manager_routing seeds
- corpus replay for the new targets in fuzz/shared tests
Verification: cargo test 684 passed (682 + 2 regression); clippy
-D warnings clean (main + fuzz/shared); fmt clean (main + fuzz);
cargo fuzz build clean; 20 s smoke on all three new targets clean
(manager_routing 79k, envelope_semantic 141k, spec_parse 517k runs);
crash input replays clean post-fix