Brings the older pre-ML modules up to the same standard as the
newer ML pipeline. Closes Findings 7 (file headers), 8 (inline
comments), 9 (NIH reimplementations), 11 (selectWeightedRR partial
function + O(n^2)), 12 (weak LoadBalancer tests), 13 (placeholder
Backend / ConnLimit tests), and 32 (locally-redefined HTTP status
constants).
Module rewrites with new file header + zero inline comments:
- src/Aenebris/Proxy.hs
- src/Aenebris/LoadBalancer.hs
- src/Aenebris/HealthCheck.hs
- src/Aenebris/Middleware/Security.hs
- src/Aenebris/Middleware/Redirect.hs
- src/Aenebris/Config.hs
- src/Aenebris/Backend.hs
- app/Main.hs
Algorithmic + structural fixes:
- LoadBalancer.selectWeightedRR no longer uses partial (!!) and a
separate find/maximum/index pass; replaced with a single STM fold
that tags each backend with its current weight and picks via
Data.List.maximumBy + Data.Ord.comparing. O(n) instead of O(n^2),
no Maybe fallback.
- Proxy.proxyApp deduplicates the regular-HTTP code path that was
copy-pasted across the WebSocket and `_` connection-type branches
(Finding 16). Single forwardRegular helper called from both.
- Proxy.logRequest removed; per-request stdout logging was
inconsistent with the stderr error pattern used elsewhere and a
perf hazard at scale (Finding 17). Defer real structured logging
to Phase 4 per docs/status/MASTER_PLAN_2026.md.
- Backend.recordFailure now increments rbTotalFailures uniformly
across all states (Finding 22), not just Unhealthy.
- Backend.Show instance now includes weight (Finding 21).
- Connection.hs gains microsPerSecond and httpOkStatusCode named
constants. tcUpstreamReadSeconds added to TimeoutConfig.
- HealthCheck uses the shared microsPerSecond constant instead of
inline 1000000 (Finding 18). Compares status against the named
httpOkStatusCode constant.
NIH reimplementations replaced with stdlib:
- LoadBalancer: filterM, forM_ from Control.Monad; minimumBy,
maximumBy from Data.List; comparing from Data.Ord.
- Proxy: zipWithM from Control.Monad.
- HealthCheck: zipWithM_ from Control.Monad.
- Middleware.Security: catMaybes from Data.Maybe.
- Config: nub from Data.List (replaces local nubText).
- Fingerprint.JA4H, ML.Features: built-in lookup over
[(CI ByteString, ByteString)] (CI ByteString already has Eq).
- RateLimit, DDoS.ConnLimit: intercalate ":" from Data.List
(replaces duplicated joinColons in two files).
- Honeypot: status200, status404 from Network.HTTP.Types (drops
local mkStatus duplications).
- WAF.Engine: status403 from Network.HTTP.Types.
Test strengthening (test/Spec.hs):
- loadBalancerSpec: round-robin now asserts even distribution
([3,3,3] across 3 backends in 9 rounds); weighted RR asserts the
4-weight backend wins >= 35 of 50 picks; least-connections asserts
the lower-count backend is selected after manually bumping the
other to 5 active connections.
- backendSpec: "tolerates repeated failures" replaced with a real
state-transition test ('Healthy after 1 failure, Healthy after 2,
Unhealthy after 3'). 'records successes without crashing'
replaced with assertion that recordSuccess on Healthy resets
rbConsecutiveFailures to 0.
- connLimitSpec: 'release decrements counter' now reads
currentCount before and after release to assert the count
actually went 1 -> 0.
Build clean, 358 examples passing, 0 failures, 0 GHC warnings on
any touched module. Audit findings 14, 15, 18 (residual MAJOR) and
19-31 (MINOR) deferred to subsequent passes.