chore(release): 0.8.1 — duplicate adopt/open fix + fuzz harness bookkeeping
- version 0.8.0 -> 0.8.1 (bug-fix release; semver-checks 196 pass vs 0.8.0) - CHANGELOG 0.8.1: the manager_routing-found duplicate adopt/open fix, the fuzz harness, and the verification summary - publish exclude gains docs/research/ (internal research notes; fuzz/ was already excluded — package verified clean of both) - fuzzing.md §7.9: standing no-hosted-CI policy — corpus replay is the release-verification fuzz gate; campaigns manual via the detached runner; OSS-Fuzz out; no workflow files in this repo - AGENTS.md: corpus replay added to the verification checklist - lockfiles: alkcall 0.8.1 (root + fuzz) Verification: 684 tests; clippy -D warnings (main, wasm, fuzz/shared); fmt clean; doc clean; wasm32-unknown-unknown check+clippy clean; semver-checks 196 pass; publish --dry-run 116 files (no fuzz/research/ reviews/sdd/AGENTS in the tarball); fuzz corpus replay 5/5 green
This commit is contained in:
1 parent
77f7006e2e
commit
3bda29c419
6 files changed
+107
-10
No files matched your search
@@ -194,6 +194,7 @@ Run these before committing. All must pass.
|
||||
|
||||
```bash
|
||||
cargo test # full suite
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml # fuzz corpus replay (stable)
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo fmt --check
|
||||
cargo doc --no-deps # if docs changed
|
||||
|
||||
@@ -4,6 +4,65 @@ All notable changes to this crate are documented here. The format is
|
||||
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.8.1] - 2026-09-28
|
||||
|
||||
Bug-fix release: a duplicate `adopt_channel`/`open_channel` no longer
|
||||
destroys the live channel it collided with, found by the crate's new
|
||||
fuzzing harness (`fuzz/` workspace — five cargo-fuzz targets, committed
|
||||
seed corpora, three 10-minute campaigns clean). No API or wire-format
|
||||
change; the error is still `Err(ChannelExists)` — the difference is
|
||||
entirely in what the collision leaves behind.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Duplicate adopt/open no longer replaces the live channel's state
|
||||
(found by the `manager_routing` fuzz target,
|
||||
`docs/research/fuzzing.md` §7.8).** `ChannelManager::open_channel`
|
||||
and `adopt_channel` used `HashMap::insert(...).is_some()` as the
|
||||
collision check — but `insert` *replaces* an occupied entry and
|
||||
returns the old value, so a duplicate on an in-use id installed the
|
||||
new `ChannelState`, dropped the live channel's `demux_sender`
|
||||
(spurious EOF to its readers; all subsequently routed chunks lost),
|
||||
and *still* returned `Err(ChannelExists)`. The mux write half kept
|
||||
framing onto the transport for a channel the demux no longer fed.
|
||||
Both functions now check `contains_key` and return before any map
|
||||
mutation — the live channel's routing state is untouched on
|
||||
collision. Reachable whenever an open-op response is replayed or a
|
||||
connection-owner race re-announces an id. Regression tests:
|
||||
`adopt_channel_duplicate_id_leaves_live_channel_intact`,
|
||||
`open_channel_duplicate_id_leaves_live_channel_intact` (both verify
|
||||
routing survives a rejected duplicate; the first also verifies the
|
||||
EOF sentinel remains the only EOF source).
|
||||
|
||||
### Added (development)
|
||||
|
||||
- **Fuzzing harness (`fuzz/` workspace, `docs/research/fuzzing.md`).**
|
||||
Five cargo-fuzz targets over the two attacker-reachable wire formats
|
||||
and the stateful channel-routing surface: `chunk_header` (8-byte
|
||||
header no-panic/round-trip/accounting), `envelope_frame` (frame
|
||||
decode error-shape partition + allocation bound), `manager_routing`
|
||||
(`Arbitrary` op sequences over `ChannelManager` with exact counter
|
||||
models and the parked-bytes bound), `envelope_semantic` (all six
|
||||
event kinds: constructors → serde → framing round-trip), and
|
||||
`spec_parse` (attacker-shaped op-register schemas compile or reject
|
||||
cleanly at registration). Invariant logic lives in
|
||||
`fuzz/shared/` — a stable-toolchain crate replayed as plain
|
||||
`cargo test`, so the committed corpora stay executable without
|
||||
nightly. Campaigns (10 min per target, detached runner): ~25M
|
||||
executions total, zero crashes/hangs/OOMs. The `manager_routing`
|
||||
campaign found the 0.8.1 fix above within minutes — exactly the
|
||||
stateful interleaving class example-based tests cannot reach.
|
||||
`fuzz/` is excluded from the workspace and from the published
|
||||
package.
|
||||
|
||||
### Verified
|
||||
|
||||
- 684 tests pass; `clippy --all-targets -- -D warnings`, `fmt --check`,
|
||||
`cargo doc`, wasm32-unknown-unknown check, `cargo semver-checks`
|
||||
(no API changes vs 0.8.0), and `cargo publish --dry-run` clean;
|
||||
corpus replay (`cargo test --manifest-path fuzz/shared/Cargo.toml`)
|
||||
green.
|
||||
|
||||
## [0.8.0] - 2026-09-18
|
||||
|
||||
Review 008's remediation lands in full — the graduation upstream asks
|
||||
@@ -678,6 +737,7 @@ Vendored core types (`Connection`, `ProtocolHandler`, `BiStream`,
|
||||
(ADR-046), the channels protocol with openable-ALPNs-as-operations
|
||||
(ADR-047), and the `ChannelClient` transport-agnostic client.
|
||||
|
||||
[0.8.1]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.8.1
|
||||
[0.8.0]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.8.0
|
||||
[0.7.1]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.7.1
|
||||
[0.7.0]: https://git.alk.dev/alkdev/alkcall/releases/tag/v0.7.0
|
||||
|
||||
Generated
+1
-1
@@ -27,7 +27,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkcall"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alkcall"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.88"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -9,7 +9,7 @@ readme = "README.md"
|
||||
repository = "https://git.alk.dev/alkdev/alkcall"
|
||||
keywords = ["rpc", "json-rpc", "multiplexing", "wire-format", "alpn"]
|
||||
categories = ["network-programming", "asynchronous", "encoding"]
|
||||
exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/sdd_process.md", "fuzz/"]
|
||||
exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/sdd_process.md", "docs/research/", "fuzz/"]
|
||||
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
**Status:** steps 1–3 of §7.4 adopted — `fuzz/` workspace, five targets,
|
||||
committed seeds, detached runner, two-tier campaigns run; one real
|
||||
finding (duplicate adopt/open destroying the live channel) fixed with
|
||||
regression tests. CI deferred until a platform exists (§7.8)
|
||||
regression tests. §7.2/§7.4's CI tiers are **not planned** — this repo
|
||||
will not run hosted CI (the git host is a minimal gitea that serves git
|
||||
only; see §7.9). CI's two deliverables are covered instead: the stable
|
||||
side's corpus replay (`cargo test --manifest-path fuzz/shared/Cargo.toml`)
|
||||
runs as part of every release's verification checklist (AGENTS.md), and
|
||||
campaigns run manually via the detached runner (§7.6)
|
||||
**Date:** 2026-09-27
|
||||
**Research inputs:** web survey of the 2025–2026 Rust fuzzing ecosystem, survey
|
||||
of fuzzing practice in comparable Rust protocol crates (rustls, quinn, quiche,
|
||||
@@ -380,11 +385,15 @@ comparisons), and a `-dict` of JSON tokens for the envelope targets.
|
||||
found; triage §6.2 candidates with targeted corpus entries.
|
||||
**Done for targets 1–2 — see §7.7.**
|
||||
3. Add targets 3–5, the smoke CI job, and `.gitignore` entries.
|
||||
**Targets 3–5 done, campaigns clean — see §7.8. CI deferred (no CI
|
||||
platform exists in this repo yet; the stable-side corpus replay
|
||||
`cargo test --manifest-path fuzz/shared/Cargo.toml` is the drop-in
|
||||
smoke gate when a platform is chosen).**
|
||||
4. Scheduled campaign tier; then OSS-Fuzz application.
|
||||
**Targets 3–5 done, campaigns clean — see §7.8. The CI job will not
|
||||
exist — see §7.9 (no hosted CI policy); the stable-side corpus
|
||||
replay `cargo test --manifest-path fuzz/shared/Cargo.toml` is in
|
||||
the release verification checklist instead (AGENTS.md).**
|
||||
4. ~~Scheduled campaign tier; then OSS-Fuzz application.~~ **Not
|
||||
planned — see §7.9.** Long campaigns are run manually via the
|
||||
detached runner (§7.6) when wanted; OSS-Fuzz is out (it requires
|
||||
hosted infrastructure and a public repo posture this project does
|
||||
not have).
|
||||
|
||||
### 7.5 Relationship to existing tests
|
||||
|
||||
@@ -733,6 +742,33 @@ artifacts directory — no crashes, hangs, OOMs, or leaks.**
|
||||
and held; §6.2-3 confirmed bounded at the parser level (§7.7). The
|
||||
stateful coverage §7.4 step 3 called for exists and is clean.
|
||||
|
||||
---
|
||||
|
||||
## 7.9 No hosted CI — the standing policy (2026-09-28)
|
||||
|
||||
§7.2's two CI tiers (per-push smoke, scheduled campaign) and the
|
||||
OSS-Fuzz step assume a hosted CI platform. This repo has none and will
|
||||
not get one: the git host is a minimal gitea that serves git and
|
||||
nothing else, deliberately — gitea/gitlab have had full-compromise CVEs
|
||||
(app.ini and any plaintext DB tokens exfiltrated in one real incident
|
||||
elsewhere), so the attack surface stays minimal. All verification and
|
||||
campaigns are **manual, run from the separate publishing server**:
|
||||
|
||||
- **Corpus replay is the fuzz gate, as plain `cargo test`:**
|
||||
`cargo test --manifest-path fuzz/shared/Cargo.toml` replays every
|
||||
committed seed through the same invariant functions the fuzzer runs
|
||||
— on stable, no nightly, no cargo-fuzz. It is part of the release
|
||||
verification checklist (AGENTS.md). This is CI tier 3's deliverable
|
||||
(§7.2), minus the automation.
|
||||
- **Campaigns** run via the detached runner (§7.6) when wanted —
|
||||
e.g. before a release, or after touching `src/protocol/wire.rs`,
|
||||
`src/channels/wire.rs`, the demux loop, or `ChannelManager`.
|
||||
- **OSS-Fuzz is out**: it requires hosted infrastructure and a public
|
||||
repo posture this project does not have.
|
||||
- Do not add Actions/Gitea-Actions/workflow files anywhere in this
|
||||
repo, and do not reintroduce "when CI exists" language into docs —
|
||||
point at this section instead.
|
||||
|
||||
## 8. Answering the "if / how" directly
|
||||
|
||||
- **If?** Yes — justified by position in the dependency graph, two stable
|
||||
|
||||
Generated
+1
-1
@@ -27,7 +27,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkcall"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
||||
Reference in new issue
Block a user