RateLimit header fields
How PermDock's HTTP adapters answer an exhausted limit grant with 429, Retry-After and the IETF RateLimit and RateLimit-Policy header fields, pinned to draft-ietf-httpapi-ratelimit-headers-11.
Draft posture: build (draft-ietf-httpapi-ratelimit-headers-11, 23 May 2026; Retry-After from RFC 9110 and the Problem Details .../rate-limited body are the stable twins, per watch list)
What it is
The IETF HTTPAPI working group's RateLimit header fields draft defines two response fields a server uses to tell a client about its quotas:
RateLimit-Policydescribes a quota policy: a name, the quotaqand the windowwin seconds ("daily";q=1000;w=86400). Several policies are a comma-separated list.RateLimitdescribes what is left under a named policy right now: the remaining unitsrand the secondstuntil the quota resets ("daily";r=0;t=3600).
Both are Structured Fields (RFC 9651) lists whose items are the policy name as a string. The draft sits next to Retry-After (RFC 9110 section 10.2.3) and the 429 Too Many Requests status (RFC 6585), which it does not replace.
Why it matters for PermDock
A limit grant is how a policy caps a paid or agent-reachable action (limits). When the count runs out, the caller needs to know two things: that it should back off rather than ask for more access, and for how long. A 403 says neither, so an agent that treats a 403 as "try another permission" keeps hammering the route. A 429 with Retry-After says "wait", and the RateLimit fields say how big the quota is, so a client can pace itself before the next exhaustion.
How PermDock uses it
allow(permissions.report.export, { limit: { count: 100, per: "day" } });When that grant is exhausted, an HTTP adapter built on the server kernel answers:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 3600
RateLimit: "member";r=0;t=3600
RateLimit-Policy: "member";q=100;w=86400
{ "type": "https://permdock.com/problems/rate-limited", "status": 429, "permission": "report.export",
"denials": [{ "role": "member", "reason": "limit" }], ... }- Where the numbers come from. The
limitdenial carriesdetail: { count, window, resetsAt }(LimitDetail), computed by the evaluator from the grant and the window it counted in. The renderer reads nothing from theLimitStore, so rendering a response never touches the counter. - One policy per exhausted grant. Each role whose limited grant matched and was exhausted is one policy, named by the role (
"default"for a top-level grant): an RFC 9651 sf-string, with"and\escaped and anything outside printable ASCII percent-encoded as UTF-8, so a role namedélèveis"%C3%A9l%C3%A8ve".Retry-Afteris the smallestt, the soonest any of them frees a call. - Only when every denial is
limit. A decision where another grant was denied for a different reason stays a403.../denied; a429would tell the client that waiting helps when it does not. limit-unavailableis a503. No store, a store that threw, or a store that returned a Promise denies withlimit-unavailable(fail-closed). That is the server's fault, not the caller's, so it renders as503.../limit-unavailablewith noRetry-After.- Granted responses carry no fields yet. A granted decision carries
quota: { remaining, resetsAt }; an app that wantsRateLimiton every response sets it fromquotain its handler. - tRPC and oRPC map
429toTOO_MANY_REQUESTSand503toSERVICE_UNAVAILABLE.
Mapping
| Draft concept | PermDock |
|---|---|
| Quota policy | A limit grant under one role |
| Policy name | The role name, or "default" for a grant without one |
q (quota) | limit.count |
w (window) | limit.per in seconds (LimitDetail.window) |
r (remaining) | 0 on a denial |
t (reset) | LimitDetail.resetsAt minus now, at least 1 |
429 + Retry-After | Reason limit on every denial |
| Service unavailable | Reason limit-unavailable, 503 |
Sources
- draft-ietf-httpapi-ratelimit-headers-11, RateLimit header fields for HTTP, IETF HTTPAPI WG, 23 May 2026
- RFC 9110 section 10.2.3,
Retry-After - RFC 6585 section 4,
429 Too Many Requests - RFC 9651, Structured Field Values for HTTP
Last updated on
RFC 9457 Problem Details
The application/problem+json body PermDock's HTTP adapters return for denied and approval-required decisions, and why it is written for humans and models alike.
Postgres row-level security
Postgres RLS as a compile target and import source for PermDock policies, covering CREATE POLICY semantics, Supabase and Neon helpers, GUC patterns, pg_policies introspection, the Drizzle and Prisma 8 authoring surfaces, testing and the risks of round-tripping.