Budgets — revocation reaches the meter (base protocol §Token Revocation, budgets §Token Scope; drain rule per AAuth issue #151, raised from our production and not yet in the editor's copy):
InMemoryMeter.revoke(iss, jti): withdraws the grant so no new request can reserve against the token; requests already in flight complete and are committed as usual. Idempotent;Falsefor an unknown(iss, jti).TokenRevoked(drained): whatreserve()returns for a revoked token.drainedis true once nothing is in flight on it.- Final record only after the drain:
consumed_record()withholds a revoked token's record while requests are in flight, so a record the resource issues after the revocation is the token's final figure and the issuer can settle on it (the ordering is checkable byiat). make_revocation_endpoint(meter, authenticate_ps=…): the base protocol's endpoint — signedPOST {"iss","jti"},200empty on success or already-invalid,404unknown,403when the caller is not the issuer.BudgetMiddlewareanswers a revoked token with the plainrequirement=auth-tokenchallenge (code: AUTH_TOKEN_REVOKED, noAAuth-Budget), the resource token carrying the final record once drained.revocation_state(key, jti)for resources that learn of revocation out of band (e.g. from their own registry) and want the same challenge.
Budgets — the final-record challenge for expired auth tokens (draft §Budget Exhaustion "auth token expired" + §Settlement). The final/snapshot settlement rule needs a moment at which the resource states a token's final figure — and that moment is the challenge to an expired token. A verifier that refuses to look at an expired token can never build it, so the issuer never sees a final record and accounts every allocation as fully consumed forever.
BudgetMiddlewareanswers a genuine-but-expiredaa-auth+jwt(issuer signature and proof of possession verified, request signature fresh) with401+ plainAAuth-Requirement: requirement=auth-token;resource-token=…(noreason— the budget didn't run out, the token did) andcode: AUTH_TOKEN_EXPIRED. The resource token carries the presented token's{jti, consumed}; noAAuth-Budgetheader. Nothing is served or metered.HttpsigVerifier.verify(..., allow_expired_auth_token=True)— opt-in, default False: returns the token withVerifiedSignature.expired=Truefor up toEXPIRED_AUTH_TOKEN_GRACE(2h, the meter's retention) afterexp. Access decisions throughverify()/require_signatureare unchanged: an expired token is stillNonethere, and the middleware never caches an expired result for downstream dependencies.build_aauth_requirement(reason=None, …)emits the reason-less challenge.
AAuth Budgets — per-token cap, one consumption record (draft-hardt-aauth-budgets editor's copy, September 2026; issue #120):
- No more pooling.
InMemoryMetercaps each auth token at its ownbudget(§Aggregation): committed consumption plus outstanding reservations against the presentedjtinever exceed its grant, and a token never draws on a sibling's allocation. The(iss, sub, aud)key survives as the per-person ledger for records and usage counters — it is not a second ceiling. 0.4 pooled a person's live grants into one purse, which let a jti spend past its own grant; the overflow, spent from a sibling's allocation, was never attributed and the sibling's remainder later released as unspent. Therequiredmember makes a fragmented agent's re-authorization a calculation, so the purse bought nothing worth that. - One record on the wire. Budget refusals carry a single
budget_consumedobject —{"jti", "consumed"}for the PRESENTED token — per §The Consumption Record. NewInMemoryMeter.consumed_record(key, jti);consumed_records(key, jkt=…)stays as the audit view. - Breaking:
InMemoryMeter.remaining(key)→remaining(key, jti);resource_token_provider(key, records: list)→(key, record: dict | None).InsufficientBudget.remainingis now the presented token's balance. - Test vectors regenerated (
consumption_recordsis one object).
AAuth Budgets — the August 20 editor's-copy additions (allocation model,
omitted cost, required, usage counters):
requiredmember: aninsufficient-budgetrefusal now carries the refused request's maximum cost inAAuth-Budget, so the agent's retry is a calculation (fit the bound toremaining) rather than a search.- Streaming / cost-omitted (§cost-omitted): a streamed response states
reserved(REQUIRED whencostis omitted) withremainingalready net of the hold, commits when the stream ends (request.state.budget_costmay be set mid-stream), and the agent recovers the exact figure from the next response'sremaining. SSE is recognized automatically; other streams opt in withrequest.state.budget_streaming = True. - Usage endpoint (§Usage Counters):
make_usage_endpoint(meter, …)— scope queries (subcalendar counters: day/week/month/year/all_time on UTC boundaries) and per-keyjktsqueries; unrecognized scope values omitusage(never zero — a query must not reveal whether an account exists); unrecognized/pruned thumbprints are omitted fromjkts(never zero — a false zero misleads an allocation decision); per-key figures pruned on 24h IDLE, so a key in continuous use is never pruned; figures keyed by the issuing PS, so the endpoint structurally answers only the party whose tokens were accepted. PS authentication is pluggable (authenticate_ps). - Signed usage responses (§The Signed Response):
ResponseSigner— an Ed25519 HTTP Sig over@status,content-type,content-digest, bound to the request via@authority/@pathwith thereqparameter. validate_budget_grant: the §Resource Metadata MUSTs (only declared units; declared decimals) as a pre-mint guard against the draft's "thousandfold error".
AAuth Budgets (draft-hardt-aauth-budgets, editor's copy) — the resource side, first known implementation (running in production on get4agent.com):
- Auth tokens (
typ: aa-auth+jwt): PS-issued budget carriers verified against a configuration-pinned PS (HttpsigConfig.trusted_ps: issuer → JWKS URL),aud-checked againstresource_url,cnf-bound, ≤1h lifetime. BudgetClaim+InMemoryMeter: atomic reserve → commit → release pooled per the draft's(iss, sub, aud)aggregation key; conservative crash-safety (an unresolved reservation counts as consumed); consumption records scoped to the presenting agent'sjktso one agent never learns about a sibling's spending.BudgetMiddleware(FastAPI):price_fnhook, metering cycle, refusals (401 +AAuth-Requirementwithreason=insufficient-budget/budget-exhaustedand an optional resource token viaresource_token_provider), per-responseAAuth-Budgetheader; error responses release the reservation.build_aauth_budget_header/build_aauth_requirement— RFC 9651 serialization (note: the field is a Dictionary, so members are comma-separated; the draft's §11 example shows parameter separators).
AAuth draft -11 support (per the editor's copy, ahead of datatracker publication):
- Fully-specified algorithms (RFC 9864):
Ed25519accepted everywhere (registered with PyJWT, including JWKS entries PyJWK cannot parse). NewHttpsigConfig.require_fully_specified_algsenforces the -11 MUST NOT on the polymorphicEdDSA; the default keeps accepting it while the -10 ecosystem migrates, and will flip when -11 posts. - Person tokens (
typ: aa-person+jwt): PS-issued, per-resourceaud,cnf-bound, ≤1h lifetime — verified via{iss}/.well-known/aauth-person.json. Opt-in: setHttpsigConfig.resource_url(the token'saudmust name it). Result scheme:"aauth-person",sub= the PS's directed user identifier. - Strict mode also enforces the -11 requirement that
cnf.jwkcarries a fully-specifiedalgmember.
- AAuth: tolerate absent
keyid(RFC 9421 makes it optional; the key comes from the token'scnf.jwk). Exposed by cross-library interop with christian-posta/aauth-signing, whose signers correctly omit it; that signer's exact keyid-less shape is now pinned in CI.
Initial release, extracted from Regent Protocol's production marketplace (get4agent.com), where it authenticates self-onboarding AI agents.
HttpsigVerifier— RFC 9421 verification for both agent dialects:- Web Bot Auth (draft -05): sf-dictionary
Signature-Agentwith;key=member selection AND the legacy sf-string form OpenAI ships in production; key discovery via/.well-known/http-message-signatures-directory. - AAuth (identity-based mode,
[aauth]extra):aa-agent+jwtinSignature-Key, issuer JWKS discovery,cnf.jwkproof of possession. - SSRF-guarded directory fetching (https-only, public-IP-only, no redirects, size-capped) with bounded per-instance caching.
- Web Bot Auth (draft -05): sf-dictionary
EgressSigner+regent-httpsig keygen— sign outbound agent traffic (Web Bot Auth), generate keys and ready-to-publish well-known files.- FastAPI integration (
[fastapi]extra):SignatureDep(enrichment) andRequiredSignatureDep(authentication with a self-explaining 401). - Test suite pinned to the official RFC 9421 B.2.6 vector, both Web Bot Auth appendix vectors (A.2.2 re-signed — the draft's printed signature does not verify over its own base; reported), and full sign→verify roundtrips for both dialects.