Files
RustProxy/docs/V0.22.2_AUDIT.md
T
2026-10-03 00:27:09 +01:00

44 lines
4.9 KiB
Markdown

# RustProxy 0.22.2 specification audit
## Scope
This audit covers the 0.22.2 delta over 0.22.1: replay-safe backend failover retry, explicit HTTP/2 upstream request handling, bounded per-backend request concurrency, richer upstream diagnostics, an expanded backend operations view, and public X.509 expiry metadata. The retained 0.22.1 RFC 8441/h2c-listener/tunnel work and 0.22.0 cache work remain historical evidence for retained functionality.
## Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| Retry only replay-safe methods | `proxy/mod.rs::retry_safe_method` permits GET/HEAD/OPTIONS only | proxy source regression; compiler gate open here |
| Never replay body-bearing writes | retry requires an exact empty request body; POST/PUT/PATCH fail the method gate | source regression + release checks |
| Retry another backend | `Pool::select_retry_wait(..., excluded)` excludes attempted IDs | `failover_selection_never_reuses_an_attempted_backend` |
| Bounded retry policy | `RetryPolicy.max_retries` validates 0..3; bounded 5xx list | balance policy regressions |
| Explicit H2 upstream | explicit `protocol=http2` uses HTTP/2 request versioning | proxy regression + real Hyper h2c upstream test source |
| Per-backend request concurrency | `PoolMember.max_concurrent_requests`, atomic full-exchange lease acquisition | `backend_concurrency_limit_queues_then_rejects_and_recovers` |
| Bounded capacity queue / 503 | `queue_timeout_ms` validates 0..60000; async selection waits in bounded intervals and returns `SelectionFailure::Capacity` | capacity timeout regression |
| Reject vs queue-timeout observability | `capacity_rejections_total` always counts final capacity rejection; `queue_timeouts_total` only counts an elapsed non-zero queue | `zero_capacity_queue_timeout_rejects_without_counting_a_queue_timeout` |
| No false TCP connection claim | limit is documented and named as concurrent proxy requests/exchanges, not `max_connections` | TODO/roadmap explicitly keep socket-count limit open |
| Failure classification | DNS, TLS, connect, response-header timeout, read/send timeout and generic I/O flow through `Lease::error_detail` | source contract checks + detailed failure regression |
| Last failure | backend view exposes class, bounded detail and timestamp | `detailed_backend_failures_are_exposed_without_losing_aggregate_counters` |
| Health ejection/recovery timestamps | health transitions store last ejected/recovered timestamps | source contract checks |
| Backend operational view | dashboard combines status, active/limit, requests/errors, latency, traffic, last failure and last recovery | JS/language/source checks |
| X.509 validity metadata | public leaf PEM parser extracts SAN, issuer, notBefore/notAfter and days remaining without reading keys | X.509 time + generated-certificate regression sources |
| Expiry warning | inventory reports `ok`, `warning` (<=30 days), `expired`, or `unknown` | source/OpenAPI/UI checks |
| Renewal status in certificate view | inventory flattens current renewal status, last check and last failure message | OpenAPI/UI/source checks |
| Full downstream H2 -> RustProxy -> upstream H2 interoperability | implementation path present | **OPEN:** live/compiler-enabled E2E required |
## Capacity semantics
`max_concurrent_requests` is a full proxy-exchange permit. The permit remains held for the response body/tunnel lifetime through the existing backend lease, preventing a streaming response from releasing capacity after headers only. A value of `0` preserves the historical unlimited behavior. `queue_timeout_ms=0` rejects immediately when no eligible backend has capacity; a positive value performs a bounded wait and then returns HTTP 503.
Pool readiness remains a health/availability signal rather than a transient-capacity signal. Backup members can receive traffic when eligible primary members are saturated. Sticky affinity is honored only when the matched backend has capacity; otherwise normal eligible selection can move the request to another backend.
A physical TCP connection/socket cap is deliberately not claimed in 0.22.2 because Hyper connection pooling requires a distinct transport-level mechanism.
## Certificate safety
Certificate inventory parsing opens only the configured public certificate PEM. Private keys are not opened by the metadata path. The parser is deliberately narrow: it extracts the leaf issuer, RFC 5280 validity values and DNS SAN coverage while rustls remains responsible for certificate/key validation in serving paths.
## Release gate
This packaging workspace has no `cargo`, `rustc` or `rustfmt`, and outbound DNS is unavailable, so Rust parsing/type checking, compilation, linking and execution cannot be claimed here. Rust-independent JavaScript/JSON/language/source/package/Go gates are run locally. A Rust-enabled release host must still run `./dev.sh check`, `./dev.sh test`, release build and live H1/H2/H3/H2-to-H2 acceptance before deployment.