feat(rate_limit): Limiter API redesign per reprazent review (Spec 8)
Summary
Addresses all 10 unresolved threads from MR !270 (merged) review and Bob Van Landuyt's spec update (2026-04-28T09:03). Implements Spec 8 (#28792).
- Limiter class — holds Evaluator at init; no per-request Evaluator allocation (Scenario A)
- Configuration class —
Labkit::RateLimit.configure { |c| c.redis = ...; c.logger = ... }(Scenarios M, N) - Result object —
checkreturnsResultwithmatched?,exceeded?,action,rule,error?(Scenarios I-K, S) - Compound Redis key — all characteristics joined into one key per rule; one
incrper call (Scenario B) - First-match-wins — evaluation stops at the first matching rule; caller controls priority by rule order (Scenarios E, F)
_unknown_sentinel — nil/empty characteristic values use"_unknown_"in the key (Scenario C)- Rule
name:field — added as required field; used in Redis key instead of positional index (Scenario Q) - Callable limit/period — lambdas resolved at check-time, not init (Scenario D)
- Remove
KNOWN_CHARACTERISTICS— any identifier key is valid as a characteristic (Scenario O) - Endpoint normalisation in
Identifier#initialize— removed fromEvaluator(Scenario P) - Name validation at
Limiter.new— raisesArgumentErroreagerly on invalid names (Scenario U)
Test plan
-
bundle exec rspec spec/labkit/rate_limit_spec.rb spec/labkit/rate_limit/ --format documentation-- 77 examples, 0 failures -
bundle exec rubocopon all changed files -- no offenses
Closes #28792
/cc @reprazent for primary review (these are your unresolved threads)