- bump alkcall 0.8.1 -> 0.9 (gateway feature unchanged). No code change
forced: none of the 0.9.0 breaking surfaces are constructed or
pattern-matched in this crate — no ManagerError match arms (the new
InvalidAdopt variant changes nothing here), no RelayError matching
(the removed PubTypedOpen variant is unreachable from this crate),
no Establishment construction or Debug dependence (the WS ferry
passes Option<OpenEstablisher> through unchanged, per the 0.8.0
audit), and the op/register forward-through refactor does not touch
the from_wss import filter or the adapter-registered leaf bundles
- ride-through fixes: tests/ws_overlay_ops.rs now expects
register_imported's new Result<(), String> return (8 sites assert
registration success instead of discarding the value — the bool
return they consumed was ignored anyway); fuzz/shared follows the
main crate to alkcall 0.9 so the harness replay resolves one
alkcall version (the stale 0.8.1 spec pinned two copies against
alkhttp's requirement and broke the shared-crate build)
- minor bump 0.6.0 -> 0.7.0 per the dep-wave convention (0.3.0 rode
alkcall 0.6.0, 0.4.0 rode 0.7.0, 0.5.0 rode 0.8.0): this crate's
public API carries alkcall types through re-exports and the
ProtocolHandler impl, so a semver-breaking upstream is
semver-relevant here
Verification: cargo test 441+70 pass (default), 520+70 pass
(all-features), clippy --all-features --all-targets -D warnings clean,
fmt --check clean, doc --no-deps clean, fuzz corpus replay 21/21
The sse_parser re-frame probe emitted 'data: <line>' unguarded, so a
payload CR (interior CR in multi-line data: the payload line is '\r')
was consumed by strip_cr at re-parse time and the round-trip lost it.
Wave 1 had skipped only the terminal-CR case. Found ~32 min into the
first 45-min sse_parser campaign.
- frame_event emits 'data: <line>\r': the guard CR is the line
terminator, strip_cr removes exactly it, the payload CR survives
- drop the terminal-CR skip; the round trip is now total over
JSON-encodable payloads (JSON never encodes a raw CR, so the
residual asymmetry is the serde_json non-preserved key-order class)
- no crate-side change: the parser was correct (data: x\r\r\n ->
x\r); only the harness re-encode was weak
- regression: 2 new seeds (interior CR via \r escape; raw CR via CRLF
framing, two-line doc) + unit cases
Verification: shared 21/21 (default + no-default-features), main
crate green, clippy -D warnings clean, fmt clean, cargo fuzz build
clean
Two stateful op-sequence harnesses (fuzz/shared):
- ws_session_opseq: the WS byte adapter's generic pumps driven
end-to-end over a mock socket flavor through the new
byte_adapter::fuzzing drive hub (FuzzMockSocket flavor,
FuzzChannels/FuzzWriteSide channel bundle, pump spawn funnels,
FuzzInboundChunkProgress). Asserts lossless inbound forwarding
(exact after the teardown joins), the WS-01 walk's completed-chunk
count vs the declared frame count, the WS-15 close mapping
(1002/1011/verbatim-requested + per-cause reasons), lossless write
framing onto the declared chunk stream (FWD-21's failure-carry
included), lane-failure isolation (the alkcall 0.8.1 class), and
pump termination within the bound.
- retry_throttle: the inlined RetryAfterMiddleware's throttle map
through the new client::fuzzing hub — capacity bound, the
expired-first + Farthest-future eviction policy, the ceiling clamp,
refresh contraction, the Retry-After parse partition, and the
jitter bounds.
16 committed seeds (8 per target) in gen_fuzz_seeds.py, hand-encoded
against the arbitrary 1.4.x derive layout with ceiling-division
variant tags and per-element keep-going bytes, pinned by
seed-decode tests. fuzz/shared gains bytes/futures/tokio/url/
http/httpdate/arbitrary deps.
Verification: main crate 441 default tests, fuzz shared replay 21/21
(default + no-default-features), cargo fuzz build clean (5 targets),
clippy+fmt clean both crates.
FWD-21, found minutes into the first ws_session_opseq campaign: one
feed that drains a complete chunk and then hits an oversize header
(or the pending cap) in the same call returned
Walk::Oversize/PendingOverCap and silently discarded the already
drained pieces — the walk's own consumption-accounting contract
('every parsed byte is either emitted or retained', wave-1
invariant 6) was violated by the crate. Production could not reach
the shape (the mux's per-write invariant caps pending at one chunk +
header), so impact was nil, but WriteChunkWalk is public API and the
wave-1 split_walk equality check was skipped on exactly this path.
- Walk::Oversize(Vec<Vec<u8>>) / Walk::PendingOverCap(Vec<Vec<u8>>)
carry the pre-failure pieces (user-approved semver-relevant API
change)
- run_write_pump emits the carried pieces before fail_write_pump, so
a 1011 close never swallows a legal chunk the mux already counted
as written
- the ws_chunk_framing harness's split-drive failure arm now
reconciles the parsed-before-failure bytes across the whole and
split drives; a regression seed covers the drain-then-fail shape
Verification: main crate 441 default / 520 all-features tests, fuzz
shared replay 21/21 (default + no-default-features), cargo fuzz build
clean, clippy+fmt clean both crates.
Adopt the alk*/fuzzing posture (alkcall 0.8.1 precedent) — the dep
bump this ride-along builds on was 4005517:
- docs/plans/fuzzing.md: the four-target plan (sse_parser,
ws_chunk_framing this session; spec_import + the stateful wave
later), the infrastructure blueprint, and the corpus/campaign
policies (§3's table tracks progress)
- fuzz/ workspace (the sibling layout): nightly-pinned subtree,
stable fuzz/shared invariant library (corpus replay = the standing
fuzz gate), detached runner (never a foreground child of an agent
session), JSON dict, deterministic seed generator; 80 committed seeds
- two wave-1 targets: sse_parser (SseParser::feed + the FWD-17
envelope mapping — the parser every subscription forwards through)
and ws_chunk_framing (WriteChunkWalk, the write pump's chunk walk)
- crate-side surfacing: SseParser/SseEvent/sse_event_envelope are pub
behind the adapters::forward::fuzzing re-export hub; the write
pump's inline chunk loop extracted as WriteChunkWalk (pump byte
behavior unchanged — 441 tests green); retained_bytes() diagnostic
(the alktunnels/alksocks posture)
- root Cargo.toml gains the explicit [workspace] table with
fuzz/ excluded (auto-discovery would pull the nightly-consumed
dev-deps into the stable build — the alkcall trap); fuzz/ joins the
publish exclude list
- AGENTS.md verification checklist gains the corpus-replay line
FWD-20 fix, found by driving the new sse_parser harness: feed()
rescanned the whole reassembly buffer per feed — a dribble-fed
subscription (one unterminated line over many small chunks) cost
quadratic CPU (1 MiB dribble ≈ minutes per connection, unbounded
total). Line assembly now walks only the arriving chunk: the buffer
holds just the current line, the scan never revisits drained bytes.
1 MiB dribble: ~5 min (quadratic) → 21 ms (linear). Regression test
sse_parser_dribble_feeds_do_not_rescan_the_buffer.
Verification: cargo test 441 passed; cargo test --all-features ok;
clippy --all-targets -- -D warnings (main + fuzz/shared) clean;
fmt --check (main, fuzz, fuzz/shared) clean; cargo fuzz build clean;
corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml) 80
seeds green.
Refs: alkcall docs/research/fuzzing.md (§7.9 posture), alksocks
08a3694 (the sibling wave-1 shape), docs/plans/fuzzing.md §2