v2.5.1
Code quality patch — behavior-preserving refactor of the transport, storage, auth, and integration layers accumulated through v2.5.0, plus six correctness and security fixes. No wire-protocol, manifest schema, or public API breaking changes.
Deprecated import paths keep working via shims until v2.6.0.
Fixed
revoke_cascadeatomicity: delegation revocation now runs in a single SQLite transaction (BEGIN→walk→COMMIT, rollback on failure); the in-memory store holds anasyncio.Lockacross the cascade so concurrentregister_issuedcan't leave children un-revoked.usage_eventsschema: one canonical DDL owner instate/stores/sqlite.py; both indexes created withIF NOT EXISTSregardless of store init order, fixing schema/query-plan divergence.- Host-JWT verifier: collapsed the divergent verifiers into one
verify_host_bearer; revoked hosts now return 403 (was 401) on every protected route, including/asap/agent/reactivate. - WebSocket OAuth2 bypass:
/asap/wsno longer skipsOAuth2Middleware. In OAuth2-only deployments the WS path now requires a Bearer JWT in the handshakeAuthorizationheader and rejects with close code 4401 when absent or invalid. Deploys without an OAuth2 IdP are unchanged. - OpenAPI handler: hoisted the inline
format_www_authenticate_asapimport to module top;OpenAPIPathParameterError.__init__no longer raises (validation moved tofor_missing()/for_invalid()factories).resolve_headersstays as documented public API. - Client correlation binding: the client now asserts
response.correlation_id == request.idinsend(),batch(), and the WS recv loop, raisingProtocolCorrelationError(previously only checked non-empty).
Changed
- Storage consolidation: six SQLite backends (snapshot, metering, delegation, SLA, audit, plus the economics metering store) now subclass a shared
AsyncSqliteRepository(state/stores/_sqlite_base.py) with one WAL setup,transaction(),parse_iso, andbuild_where.server.pyuses the newMeteringStorageBridgedirectly;metering_storage_adapteris kept as a one-line shim. server.pydecomposition: split intocreate_*_routerfactories plusroutes/{health,jsonrpc,websocket,audit}.py(2256 → 1000 LOC);create_appis thin wiring. Shared request prep (parse→auth→envelope→timestamp→nonce) moved toASAPRequestHandler._prepare_request.client.pydecomposition: split into aclient/package (_core,_send,_discovery,_helpers);get_manifest/discovernow share_fetch_and_cache_manifest, andsend's Retry-After parser is extracted.from asap.transport.client import ASAPClientis unchanged.websocket.pydecomposition: split intoasap/transport/ws/(codecs,client,server,pool,_recv,_ack,_dispatch,_actions,_errors);_make_fake_requestis gone and WS dispatches envelopes directly through_prepare_request.websocket.pystays as a re-export shim.- Integrations: shared
asap/integrations/_base.pycentralizes JSON-schema→Pydantic conversion, URN-scoped skill resolve cache, and per-exception error formatting;langchain/crewai/llamaindex/smolagentsslimmed to glue-only (71–80 LOC each). OAuth2MiddlewareJWKS: delegates to one sharedJWKSValidatorinstead of its own cache; HTTP and WS now use the same validator and TTL (JWKS_CACHE_TTL_SECONDS=86400).MCPServertool registration: the untyped 5-tuple is now a frozenToolRegistrationdataclass with per-tool capability metadata.RemoteRPCError:RemoteFatalRPCError/RemoteRecoverableRPCErrorcollapse into oneRemoteRPCErrorwith anis_recoverableproperty; the old names stay as deprecated subclasses.- Identity rate limiting: the 9 inline limiter checks now use
Depends(require_identity_limiter); a missing limiter returns 503 (wasAttributeError→ 500). auth/modules:claims→jwks/middleware,utils→scopes,lifecycle→identity;WebAuthnSelfAuthVerifierdeleted (WebAuthnVerifierImplsatisfies the protocol directly).- MCP Auth Bridge: folded
asap.adapters.mcp.*intoasap.mcp.auth.*;asap.adapters.mcpstays as a re-export shim.
Removed
asap.transportroot re-exports:start_periodic_cleanup,WebhookDelivery,RetryPolicy,compute_signature, and other tuning/webhook internals are no longer re-exported fromasap.transport.__init__— import them from their owning modules (asap.transport.cache,asap.transport.webhook).asap.transport.codecspackage: inlined intoasap.transport.lambda_codec; WS framing primitives moved toasap.transport.ws.codecs. No shim (grep-verified zero external callers).NonceStore.is_used/mark_used: replaced by the atomiccheck_and_mark. Custom nonce stores should switch to the one-method protocol.OpenAPIExecutionKind.ASYNC_POLLING: pruned; a202 + LocationOpenAPI operation now classifies asSYNC.- Dead binary/base64 WS framing: frames are JSON text only.
Deprecated (remove in v2.6.0)
from asap.transport.websocket import ...— useasap.transport.wsdirectly.from asap.adapters.mcp import ...— useasap.mcp.authdirectly.RemoteFatalRPCError/RemoteRecoverableRPCError— useRemoteRPCError+is_recoverable.metering_storage_adapter— useMeteringStorageBridge.FRAME_ENCODING_BINARY— binary framing is gone; the literal remains on the shim for import compat only.
Migration
- v2.5.0 → v2.5.1: No breaking changes. Update deprecated import paths at your convenience (removed in v2.6.0). If you run an OAuth2-only deployment, note that
/asap/wsnow requires a Bearer JWT in the handshake — see migration guide.
Full Changelog: v0.1.0...v2.5.1