


Change a Rust Reverse Proxy Without Breaking Its Event Loop or Security
Use it when an AI changes, tests, or releases a multiprocess Rust proxy. It sets rules for builds and feature flags, errors and logs, event-loop and protocol behavior, tests and fuzzing, security-sensitive code, pull requests, and releases.
To use this AI rule
## Keep the Proxy's Library Layers and Dependency Direction
Treat the repository as a layered system with explicit dependency direction:
- A control-plane library owns configuration parsing, command schemas, shared state, logging helpers, and interprocess channels.
- A protocol/runtime library owns the single-threaded event loop, routing, sockets, buffers, metrics, TLS, HTTP, and proxy-protocol handling.
- A binary crate owns the supervisor, workers, CLI, configuration loading, hot reconfiguration, upgrades, and the command server.
- Integration-test and deterministic-simulation crates depend on runtime libraries but must not leak their async or test-only dependencies into production code.
- Fuzz targets may live outside the main workspace when their toolchain requirements differ.
Preserve dependency direction from runtime to control-plane code. Keep each worker single-threaded, avoid shared mutexes in the event loop, and keep production libraries free of async runtimes unless the architecture is deliberately redesigned.
## Build With the Pinned Toolchain and Test Feature Combinations
Before changing code, identify the pinned Rust toolchain and required code generators. If schema generation is a hard prerequisite, install or verify the generator before interpreting compilation failures.
Use locked dependencies for normal builds and tests. Run the formatter with the toolchain required by the repository configuration, run linting with warnings denied, and test the full workspace before release-oriented work. Use focused suites during iteration, then return to the affected workspace-level gates.
Treat feature flags as explicit compatibility cells:
- Keep the default allocator and cryptography choices documented.
- Test each supported cryptography provider independently, including compliance-oriented modes.
- Never enable test-injection hooks in production builds.
- Keep debug or trace logging, parser-tolerance, SIMD, zero-copy I/O, and telemetry features opt-in when they change behavior, footprint, or observability.
Do not claim a clean change because one default build passed. Confirm the feature combinations directly affected by the change.
## Handle Errors and Logs Without Panics and Never Hand-Edit Generated Code
Use structured library errors and add broad context only at executable boundaries. Network-controlled input must become a protocol error, connection action, metric, and contextual log; it must never trigger `unwrap`, `expect`, `panic`, or an unreachable branch on the release path.
Follow nearby ownership conventions. Prefer intent-revealing conversions when turning borrowed strings or bytes into owned values, and use ordinary cloning when the type is already an owned cloneable value.
Protocol modules must use their established contextual logging macros and stable search tags. Preserve the canonical session/request/backend envelope so operators can correlate failures. Use debug or trace for expected idle closes and noisy state; reserve warning and error levels for real protocol faults or invariant breaks.
Keep public metrics in the central metrics layer and update the user-facing configuration reference when adding or renaming one. Treat gauge underflow as a correctness bug.
Never hand-edit generated protocol bindings. Edit the source schema and regenerate them through the build. New unsafe code in a hot path requires a nearby invariant explanation and a focused test.
## Keep Socket Writes and Buffers From Stalling the Event Loop
For edge-triggered I/O, queueing bytes from a read path is not enough: explicitly signal pending write readiness. Missing that signal creates sessions that stall only at particular frame or buffer boundaries.
Keep scalar and vectored socket-write implementations structurally symmetric, including retry and partial-write behavior. On encrypted frontends, avoid a full duplex shutdown that can emit a reset and truncate already queued bytes; close the write side and let peer closure follow the normal read path.
Maintain a buffer large enough for the maximum protocol frame plus its header. Validate that configuration at startup instead of allowing a small buffer to deadlock framing code.
Keep wire-level connection state separate from buffer-owning stream state, and cross-reference them through stable identifiers with checked lifecycle rules. Frontend protocol negotiation and backend capability hints are different concepts; do not use one as an accidental gate for the other.
Flood controls must cover reset storms, continuation chains, repeated control frames, and priority abuse. Multiprocess hot reconfiguration must validate commands in the supervisor before distributing them to single-threaded workers.
## Test in Layers With Fuzzing and Replayable Simulations
Use five complementary test layers: unit tests beside modules, process-level integration tests, fuzz targets, deterministic simulation for sans-I/O state machines, and regression guards for structural contracts such as log layout.
For parsers and non-trivial state-machine functions, encode preconditions, postconditions, and positive-plus-negative assertions in debug builds. Public state-machine entry points should finish with an invariant sweep. Assertions describe internal impossibilities; adversarial network input must still return a safe protocol result.
Drive deterministic simulations with a seeded random source, virtual time, adversarial workloads, and fault injection. Record seeds so every failure can be replayed. Keep simulation-only runtime flags and dependencies scoped to the simulation crate.
Integration tests must allocate ports dynamically, read complete TCP responses rather than one segment, and use explicit deadlines or retry-until conditions instead of arbitrary sleeps. An ignored test needs a concrete environment dependency or tracked reason; never use ignore to hide a regression.
For upgrade, parser, compression, or framing changes, run the dedicated integration suite and the matching fuzz targets in addition to general tests.
## Change TLS, Parsers, and Framing Carefully With Regression Tests
Make conservative, test-backed changes in TLS setup, protocol negotiation, name binding, certificate loading, parser and header normalization, flow control, command framing, descriptor passing, metrics, and logs.
Reject ambiguous message framing rather than forwarding a representation that differs from the framing actually applied. Preserve pseudo-header ordering, content-length reconciliation, request-smuggling defenses, and correct reset or graceful-close semantics.
For command channels, validate length prefixes, partial messages, file-descriptor transfer, state replay, and buffer bounds. For proxy-protocol handling, cover partial and oversized headers as well as health-check traffic.
Do not expose secrets or sensitive values through logs or metrics. Keep counters and gauges correct on every error and shutdown path.
When changing event-loop or security code, add focused regression coverage for the exact invariant, then run adjacent protocol tests. Do not mix unrelated refactoring into the same change.
## State the Protocol and Security Impact in Pull Requests
Read the canonical lifecycle or architecture note before changing a complex protocol subsystem. Treat older documents as hints and cross-check their file references against current code.
Use scoped branches and conventional commit messages that identify the affected subsystem. Follow the repository's signature and sign-off policy. When a task authorizes publishing, push the committed branch and report the resulting CI run; otherwise stop at the locally authorized boundary.
Base changes on the current default branch unless an active integration branch is explicitly named. Pull requests should describe protocol or security impact, commands run, tests added, and documentation changes. Respect ownership and contributor-license gates.
Update changelogs and configuration references when behavior visible to users or operators changes, including configuration keys, metrics, command-line flags, and upgrade requirements.
Before handoff, review the diff for generated files, accidental feature leakage, missing tests, stale references, and unrelated edits.
## Release Only With Approval and Test Hot Upgrades First
Treat deployment as a separate authorized step. First build the release artifact with locked or frozen dependencies, then validate configuration and run the affected readiness gates in a staging-equivalent environment.
Container and operating-system packages must install the same build prerequisites used locally, including schema generators and platform libraries. Pin the toolchain and keep test-only dependencies out of production artifacts.
For event-loop or worker-lifecycle regressions, reproduce with one worker, automatic restart disabled, and backtraces enabled. This keeps the first failure visible instead of allowing supervisor recovery to hide it.
Hot upgrades must preserve listener and session handoff invariants. Test upgrade paths explicitly before release. Do not treat a successful ordinary restart as proof that descriptor transfer or replay works.
Do not deploy to production, alter credentials, or perform destructive recovery without explicit authorization and a rollback path. Report what was built, which gates passed, what remains unverified, and the exact boundary where work stopped.
