# http-net - Stability (1.0)
> What http-net 1.0.0 commits to, what a 1.x release may change, what only a
> 2.0 may change, and the audit that 1.0.0 is released on. **Draft (0.9.2):**
> the API is frozen as the 1.0 candidate; the audit record at the end is
> filled in as each part is done, and 1.0.0 ships only when every part is
> recorded and James approves the release.
## What 1.0 means here
From 1.0.0, every public item in [`docs/API.md`](API.md), apart from those
under "Planned after 1.0", is under semantic versioning:
- **Patch (`1.0.x`):** fixes only. No API change. A fix may refuse a message
that was accepted before when accepting it is a security defect (a message
two implementations could frame differently, a limit that was not
enforced); every such change is listed under **Security** in
`CHANGELOG.md`.
- **Minor (`1.x.0`):** additions only: new functions, methods, types,
`HttpError` variants, `Limits` settings, enum variants of the
`#[non_exhaustive]` enums, and the items `docs/API.md` lists as "after
1.0". Existing items keep their signatures, their defaults, and their
behaviour.
- **Major (`2.0.0`):** anything else: a removed or renamed item, a changed
signature, a changed default limit, a message accepted in 1.x that a
2.0 refuses for a reason other than security, a raised MSRV.
A deprecated item stays for the whole 1.x series and names its replacement.
## The frozen surface
The complete list, with signatures, errors, and examples, is
[`docs/API.md`](API.md). In summary:
| Connections | `ServerConn`, `ServerEvent`, `Progress`; `ClientConn`, `ClientEvent`, `ClientLeniency` |
| Views | `RequestHead`, `ResponseHead`, `Headers`, `HeaderValues`, `ListElements`, `Version`, `BodyFraming`, `TargetForm`, `OwnedRequestHead` |
| Builders | `Request`, `Response` |
| Allocation-free tier | `RawHead`, `Span`, `FieldSpan`, `HeadFacts`, `parse_request_head`, `parse_response_head`, `Asked`, `Parsed`, `ParsedRequest`, `ParsedResponse`, `ChunkedDecoder`, `ChunkStep`, `Output`, `ResponseHeadRef`, `OutgoingBody`, `BodyPlan` |
| Vocabulary | `Method`, `MethodSet`, `status` (constants, `StatusClass`, `reason_phrase`, `status_class`, `status_allows_body`), `HttpDate`, `DateCache`, `is_hop_by_hop`, `is_h2_preface`, `VERSION` |
| Configuration and errors | `Limits` (every setting and its default), `HttpError` (every variant, `is_peer_error`, `is_usage_error`, `status`) |
| `blocking` (feature `std`) | `Server`, `ServerConfig`, `ShutdownHandle`, `Incoming`, `Connection`, `Upgraded`, `Client`, `ClientResponse`, `Error`, `get`, `post`, `serve`, `now_date` |
Which types may grow in 1.x is decided at the freeze (`dev/PLAN.md` D-63):
- Every public enum is `#[non_exhaustive]`: a match needs a wildcard arm,
and a new variant is a minor release.
- `ClientLeniency` and `blocking::Upgraded` are `#[non_exhaustive]` structs:
a new field is a minor release.
- `Asked`, `ResponseHeadRef`, `Span`, and `FieldSpan` are built by callers
with struct literals and stay exactly as they are for all of 1.x.
- Every other public struct has private fields; new methods are a minor
release.
## Behaviour guarantees
- **Strict framing.** A message that two conforming implementations could
frame differently is refused with a specific `HttpError`, never repaired.
The table of such refusals is in [`docs/SECURITY.md`](SECURITY.md). A 1.x
release never starts accepting one of them.
- **Bounded memory.** Every buffer is bounded by a `Limits` setting, checked
before the bytes it governs are buffered. Defaults change only in 2.0.
- **No panics on input.** No byte sequence from a peer makes the crate
panic; refused input is an `Err`. Checked by eight fuzz targets (24
CPU-hours each in 0.7, 60 seconds each on every CI run).
- **No `unsafe` code** (`#![forbid(unsafe_code)]`) in the crate, for all of
1.x.
- **Allocation.** The core allocates nothing per request after warm-up on a
keep-alive connection, and the allocation-free tier allocates nothing of
its own: a caller's reused buffer grows only until it fits. Both are proved by counting-allocator tests (`tests/alloc.rs`).
- **Output bytes.** The bytes the crate writes for a given response or
request are stable within a minor series; a minor release may change
them only in ways RFC 9112 makes equivalent (field order of the crate's
own fields, for example), and says so in `CHANGELOG.md`.
## Dependencies and platforms
- **Runtime dependencies:** the first-party crate `parse-net` only, by
published version, with no features. A third-party runtime dependency is
a 2.0 change, except the optional `http-types` feature planned after 1.0,
which is never a default.
- **`no_std`:** the crate builds without `std` (it needs `alloc`), checked
in CI on `thumbv7em-none-eabihf`. The `blocking` module needs `std`.
- **Platforms:** tested in CI on Linux x86_64, Windows x86_64, and macOS
(the runners' aarch64), stable and the MSRV. The core is portable Rust
with no platform code; only `blocking` touches the operating system,
through `std::net`.
## MSRV policy
The minimum supported Rust version is 1.85. It is tested in CI on every
platform. Raising it is a 2.0 change.
## Support
**Open, for James's decision at 1.0:** how long a 1.x minor release
receives security fixes after its successor ships, and the private channel
for reporting a security problem (for example GitHub's private
vulnerability reporting on the repository). Until then, fixes go to the
latest release only.
## How this contract is checked
- `cargo semver-checks` against the previous release before every release
from 0.9.1 on; a patch release must report no semver change.
- CI builds and tests every feature mode on Rust stable and 1.85 on Linux,
macOS, and Windows, and the `no_std` target.
- `docs/API.md` is checked against the build before every release.
## The 1.0 audit
Each part records what was checked, how, and the result. 1.0.0 ships when
every part says **done**, the 0.9 exit criteria hold (two consecutive weeks
without an API change after 0.9.0, D-66; the full documentation set; local
gate and CI green on every platform and the MSRV), and James approves.
### API
- Frozen at 0.9.0 (D-63). One change at the freeze: `blocking::Upgraded`
became `#[non_exhaustive]`.
- Every public item has rustdoc with an example, compiled, and run where
it needs no server, and an entry in `docs/API.md`.
- `cargo semver-checks` to 0.9.1 and to 0.9.2: no change (196 checks).
Run it with a toolchain whose rustdoc format it reads (Rust 1.95 for
cargo-semver-checks 0.47).
- **Status:** freeze done; two-week clock running from the 0.9.0 release.
### Documentation
- Every factual claim in the README, `docs/`, the changelog, and the
rustdoc was re-checked against the build for 0.9.0. The audit found four
code defects (two `Debug` outputs that printed credentials, a wrong
`progress()` value, a platform-dependent timeout kind, a `Display` text),
fixed in 0.9.0 with regression tests, and corrected the documentation
everywhere else (`dev/PLAN.md` D-69).
- Repeated on 0.9.1, with the same method (D-75): three code defects
(`outstanding()` after a failed response, the reuse queries at
`ResponseEnd`, and `HEAD` errors in the head), fixed in 0.9.2 with
tests, and 18 documentation corrections.
- **Status:** done for 0.9.2. Any code change before 1.0 repeats it for
what changed.
### Tests
- Unit, integration, property, differential (an independent reference
parser, httparse on requests and responses), counting-allocator, and
socket tests; every threat T-1 to T-42 has an attack test
(`docs/SECURITY.md`).
- The 0.9.x reviews add a regression test for each fix except the wait
in `serve` after persistent accept errors, which needs a failing
listener; a unit test that tells the lingering-close byte cap from its
time cap; and `tests/prop_split.rs` (split invariance where the default
drivers do not reach, and every budget exact at every split).
- **Status:** green at 0.9.2 (386 tests and 346 doctests).
### Benchmarks
- Measured numbers, method, and hardware in
[`docs/PERFORMANCE.md`](PERFORMANCE.md) (0.6.0); the comparison
repository is [`http-net-bench`](https://github.com/jamesgober/http-net-bench).
0.7 and 0.8 showed no measurable change against 0.6 (`dev/PLAN.md` D-61).
- Head parsing is at parity with httparse, not 20% ahead: the shortfall and
its cause are published (D-54).
- The 0.9.x pass (D-72): no regression against 0.6.0, 0.7.0, or 0.8.0
(interleaved, nine rounds); the competitor ratios of 0.6 hold on 0.9.0;
the unmet targets decomposed in `docs/PERFORMANCE.md` (the response-write
benches include reading the request; `write_head` validates each
borrowed field per call).
- **Status:** done for the frozen code; the H1 comparison is rerun on a
quiet machine before 1.0 (the 0.9.x run shared the machine with
fuzzers, so only its ratios are used).
### Security
- Fuzz campaign (24 CPU-hours per target, no finding), 8-hour soak (+3.4%
memory after warm-up), threat audit (T-21 fixed), response-side
differential: `docs/SECURITY.md`, hardening record.
- HTTP Garden: a manually triggered job (`.github/workflows/garden.yml`,
D-68) compares http-net with hyper, nginx, h2o, and gunicorn, directly
and through haproxy. The first run (2026-10-29, 679 cases) found no
defect: each of the 156 differences is a documented refusal, an input
the RFCs allow, or a limit of the harness (`docs/SECURITY.md`, D-70).
- A second, independent review of the frozen code (0.9.x polish pass,
D-73) found no smuggling, desync, panic, or memory defect; it found an
error answer to `HEAD` with content, pipelining after a response that
closes, and body bytes in `Debug`, all fixed in 0.9.1 with tests.
Every fuzz target ran again for 15 minutes on the 0.9.1 code with no
finding in the crate (one overflow in a test oracle, fixed, D-74), and
the HTTP Garden ran again with the same result as its first run.
- **Status:** done.
### Parity with other implementations
- Every cell compared with hyper, httparse, h11, llhttp, picohttpparser,
and the `http` crate, by running them where possible and from source
otherwise: [`docs/PARITY.md`](PARITY.md) (D-67).
- **Status:** done.
### Consumers
- serve-net and LexerSketch Studio run end to end against 0.8 with no
change (D-62), and again against the 0.9.2 code: `ls-studio`'s 42 tests
and serve-net's whole suite pass with no source change (the crate moved
to 0.9 on its own). The WASP gateway skeleton and the first fetch-net do not
exist yet. The specification also asks for LexerSketch Studio's publish
step to use the API end to end before 1.0.
- **Status:** open.
## Open before 1.0
What is still open after the 0.9.x polish pass, and who decides it.
Everything else in the audit above is done.
**For James:**
- The security-fix support window for 1.x minors and the private channel
for reporting a vulnerability ([Support](#support)); a root
`SECURITY.md` follows from the channel.
- The third consumer (D-62): serve-net and LexerSketch Studio run end to
end; the WASP gateway and the first fetch-net do not exist yet. Wait for
one, or ship 1.0 on two.
- Whether LexerSketch Studio's publish step must use the API end to end
before 1.0, as the specification asks.
- The 1.0 go or no-go itself, no earlier than 2026-11-12 (two weeks after
0.9.0 with no API change, D-66).
**Engineering, before the 1.0 tag** (no API effect):
- An H1 competitor run on a quiet machine (D-72).
- H6 (plaintext over sockets, through serve-net) and the report-only C
harnesses (picohttpparser, llhttp) in `http-net-bench`; the
specification lists them, none is run yet.
**Targets not met, with the decision on record** (`docs/PERFORMANCE.md`):
the latency targets for the browser GET, pipelined requests, the 1 MiB
bodies, the response write, `write_head`, and the client parse; and 20%
ahead of httparse on head parsing (D-54: parity, kept safe).