Description
The process-wide shared 0.5 rps limiter delays polling across independent REST and GraphQL quotas. Replace it with configurable per-class quotas while respecting upstream signals; no fairness, packing, interval or collector changes.
Acceptance Criteria
- #1 Replace the single process bucket with two, REST and GraphQL. Frozen keys:
cloudflare.rate_limit.rest.requests_per_seconddefault 3,.rest.burstdefault 5,cloudflare.rate_limit.graphql.requests_per_seconddefault 0.8,.graphql.burstdefault 2; finite positive rate, burst in [1,1000], env viaCF2OTEL_CLOUDFLARE__RATE_LIMIT__REST__REQUESTS_PER_SECONDand siblings. The legacy flatrequests_per_second/burst, if explicitly set, applies to both classes and logs one deprecation warning naming the new keys; never a startup failure. REST: honourretry-afterand pause the REST bucket for it so siblings do not stampede; parseRatelimit(r,t) and, whenris at or below the REST burst, defer the next REST token untiltseconds have passed; malformed headers are ignored. GraphQL: anerrors[].extensions.code == "budget"response classifies asrate_limited, pauses only the GraphQL bucket for 300 s, and retries once after the pause if the context allows. Red then green through fake upstreams with short real durations, no injected production clock: (a) a saturated GraphQL bucket does not delay REST; (b)Ratelimit: "default";r=0;t=1delays the next REST request by at least 1 s; (c) a budget error pauses GraphQL only and classifiesrate_limited. Each fails on R1’s SHA. Existing tests pass; a test asserting the old 0.5 default or one shared budget changes only to the new contract, and the report names it.docs/configuration.mdandconfig.example.yamldescribe the new keys and the header behaviour. Security review and pre-land CodeRabbit accept; exact-SHA CI green
Definition of Done
- #1 just check (fmt-check, lint, vet, test, tidy-check, build, vuln)
- #2 just ci before a change that touches the Dockerfile, goreleaser or the image (adds snapshot + image)
- #3 Every new signal or attribute name declared in internal/semconv and listed in docs/signals.md
Implementation Plan
Loop22: based on landed R1 08a6c0eb, inspect config/client; authentic short-real-time fake upstream red on R1 then green per-class/header/budget tests; implement only frozen paths, full/race gates and CodeRabbit; hold candidate before push for independent security review. No packing/fairness/interval/new collector work.
Implementation Notes
Loop22 implementation1 plus one CodeRabbit-minor reproduce/correct cycle (conservative attempts2 of4): baseline publicclient reds for class isolation/header1second/budget, fivegreen witnesses including RetryAfter sibling pause and malformed-data budget. justcheck and racecount3 green; SecurityPASS diff777ff572; CodeRabbit all12files complete zero critical/major, soleminor fixed/redgreen. Landed15c7ef311107774105c1a4d44fdb4098888dcd96 tree d5e9f0427119167181fed560f7a5e1f09083645d hashes match review. ExactCI37301213397 success; M2-composed justcheck exit0. Intended updated tests TestProcessRateLimitRepeatedConfiguration and TestReviewSharedBudgetPublicBoundary exhaust fiveREST defaulttokens; TestReviewRedirectQueueOutsideNetworkTimeout now3rps, security/lifecycle assertions preserved. Full300s successful retry unexercised; real cancellation/pause-state/class isolation proved. Valid-long headers uncapped per contract; malformed mixed extensions outside valid envelope remains untested. Evidence /tmp/cf2otel-m2-evidence/acceptance-report.json.
Final Summary
Independent REST3rps/burst5 and GraphQL0.8rps/burst2 process budgets; compatible explicit legacy keys with one warning, nested env/validation, REST rate headers and GraphQL budget retry/cancellation. Authentic red/green, complete CodeRabbit/minorfix, security, full/repeatedrace, exactCI and composed gate green. No fairness, packing, interval/newcollectors or live calls.