diff --git a/content/docs/protocol/kernel/realtime-protocol.mdx b/content/docs/protocol/kernel/realtime-protocol.mdx index 168967bcfd..298ce5154b 100644 --- a/content/docs/protocol/kernel/realtime-protocol.mdx +++ b/content/docs/protocol/kernel/realtime-protocol.mdx @@ -263,14 +263,21 @@ Subscribe to changes on a specific object: ``` **Error Response:** + +Every `type: "error"` message on this page is one `ErrorMessageSchema` envelope +(`packages/spec/src/api/websocket.zod.ts`): `messageId`, `type` and `timestamp` from +`BaseWebSocketMessage`, then a **top-level** `code` and `message`, plus an optional `details` bag +for anything else. There is no nested `error` object — a client that reads `msg.error.code` gets +`undefined`. + ```json { + "messageId": "550e8400-e29b-41d4-a716-446655440000", "type": "error", - "subscription_id": "sub_1", - "error": { - "code": "FORBIDDEN", - "message": "No permission to subscribe to 'task' object" - } + "timestamp": "2024-01-16T14:30:00Z", + "code": "FORBIDDEN", + "message": "No permission to subscribe to 'task' object", + "details": { "subscription_id": "sub_1" } } ``` @@ -667,13 +674,12 @@ Each connection consumes server resources. **Exceeding limits:** ```json { + "messageId": "6f1c2f3a-8b7d-4a1e-9c22-1a5f0d2b7e44", "type": "error", - "error": { - "code": "TOO_MANY_SUBSCRIPTIONS", - "message": "Maximum 50 subscriptions per connection", - "current": 50, - "max": 50 - } + "timestamp": "2024-01-16T14:30:00Z", + "code": "TOO_MANY_SUBSCRIPTIONS", + "message": "Maximum 50 subscriptions per connection", + "details": { "current": 50, "max": 50 } } ``` @@ -1022,12 +1028,12 @@ Handle JWT expiration gracefully: ```json { + "messageId": "9a3f5d61-2c40-4f7b-b8e1-73d5a6c9f012", "type": "error", - "error": { - "code": "TOKEN_EXPIRED", - "message": "JWT token expired", - "expires_at": "2024-01-16T14:30:00Z" - } + "timestamp": "2024-01-16T14:30:00Z", + "code": "TOKEN_EXPIRED", + "message": "JWT token expired", + "details": { "expires_at": "2024-01-16T14:30:00Z" } } ``` @@ -1035,7 +1041,7 @@ Handle JWT expiration gracefully: ```javascript ws.onmessage = async (event) => { const msg = JSON.parse(event.data); - if (msg.error?.code === 'TOKEN_EXPIRED') { + if (msg.type === 'error' && msg.code === 'TOKEN_EXPIRED') { const newToken = await refreshToken(); ws.close(); reconnect(newToken); @@ -1044,25 +1050,31 @@ ws.onmessage = async (event) => { ``` ### Rate Limiting -WebSocket messages are rate-limited: + + + **Not implemented.** Nothing rate-limits WebSocket messages today. No producer anywhere in this + repository emits `RATE_LIMITED` on a realtime path, and there is no WebSocket transport for such + a message to arrive on (see the implementation-status callout at the top of this page). The + budget below is a **planned** one, and the envelope beside it is what `ErrorMessageSchema` would + require of a producer that eventually enforces it — not a shape any code emits today. HTTP + requests *are* rate-limited, under a different code and envelope: see + [Error Handling](/docs/protocol/kernel/error-handling) for `RATE_LIMIT_EXCEEDED`. + + +**Planned limits (not enforced):** +- Max 100 messages per minute per connection +- Max 10 subscriptions per minute per connection ```json { + "messageId": "c4e7b9d2-5a31-4c8f-9e60-2b8d41f7a305", "type": "error", - "error": { - "code": "RATE_LIMITED", - "message": "Too many messages", - "retry_after": 5, - "limit": 100, - "window": "1m" - } + "timestamp": "2024-01-16T14:30:00Z", + "code": "RATE_LIMITED", + "message": "Too many messages" } ``` -**Limits:** -- Max 100 messages per minute per connection -- Max 10 subscriptions per minute per connection - ## Next Steps