feat(net): RateLimit resilience layer (Slice 1, PR 1)#76
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An absent RateScope extension previously defaulted to Scope::Global, silently skipping the endpoint's own local limit. Per ADR-0034 Amendment #1, a missing directive must now be rejected fail-closed (HttpError::Throttled, never sent); "global only" must be said explicitly with Scope::Global. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
📝 WalkthroughWalkthroughIntroduces a ChangesRateLimit resilience layer
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Closes #75
Slice 1 PR 1 of the net-http resilience layers (spec:
docs/superpowers/specs/2026-07-04-net-http-ratelimit-layer-design.md; ADR-0031 §3–4, ADR-0034). First of five layer PRs (RateLimit, then Timeout, Retry, CircuitBreaker, Tracing).What lands
RateLimit<S, K, T>+RateLimitLayer<K, T>(implnet-api::Layer) — proactive per-endpoint pacing so the stack never hits IBKR's 429 penalty box. Built from a validatedRateLimitConfig, driven bynet-api::Timer(mockable clock). Rate tokens acquired before concurrency permits (no-starvation), the token-bucket lock is released before everyawait, and a singlemax_waitdeadline bounds the whole acquire. Always returnshttp::Response<Guarded<B>>— a concurrency permit rides the body, released at stream-end/drop.RateScope<K>/Scopeper-request directive — an absent directive fails closed (Throttled, never sent; ADR-0034 Amendment ci(actions): bump the github-actions group with 3 updates #1 — a forgotten stamp must not silently fly global-only);Scope::Noneis the explicit opt-out; aLocal/Bothdirective on a bucketless or keyless request also fails closed, never reaching the leaf.LimitPolicy::TokenBucketgainsper: Durationso IBKR's1/5s/1/min/1/15minlimits are expressible with integer parameters;validate_coveragerejects a zero period.BuildError::MultipleConcurrency+validate_concurrency_singleton— the ≤1-concurrency-permit-per-request invariant (Guardedholds one) is enforced at construction, a boot failure not a silent runtime truncation.Notes
net-api::Timer,async-locksemaphore,futures-utilacquire race — notokio/hyperin non-dev deps. Tests use inline service doubles +MockTimer(deterministic clock);just cigreen.LimitPolicyperamendment and theMultipleConcurrencyboot check land here because the layer cannot pace IBKR without them.Timeout/Retry/CircuitBreaker/Tracing,stack()/build()assembly (Slice 2), and an explicit acquire-order/no-starvation runtime test (order is coded + statically verified; a deterministic no-starvation assertion needs fragile multi-task scheduling).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation