docs(protocol): drop the five WebSocket message types WebSocketMessageType does not declare - #17700
Merged
Merged
Conversation
…geType` does not declare
`WebSocketMessageType` is an exhaustive ten-member enum and `BaseWebSocketMessage`
types every message's `type` against it, so a message whose type is outside the
enum parses against no schema in the file. The realtime-protocol page taught
five types that are not members: an in-band handshake (auth, auth_success,
auth_error) and two acknowledgements (subscribed, unsubscribed).
Measured on this tree. All ten declared members show at least one producer or
consumer site under a message-type-context probe over packages/apps/examples;
all five of the above return zero there, and zero in objectui. The contract
agrees: IRealtimeService.handleUpgrade is optional and unimplemented, and
WebSocketConfig.headers is the declared seam for a credential ("Custom headers
for WebSocket handshake"). The five were documentation, not protocol.
Prose only. The page now states the declared vocabulary, puts authentication on
the upgrade request where the declared shapes put it, and shows the declared
`ack` envelope for subscribe and unsubscribe acknowledgements. No schema, no
enum and no behaviour changed; packages/spec/src/api/websocket.zod.ts is
byte-unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
This was referenced Sep 11, 2026
os-bill
marked this pull request as ready for review
September 11, 2026 16:24
os-bill
deleted the
claude/issue-17184-realtime-protocol-message-types
branch
September 11, 2026 16:54
This was referenced Sep 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #17184 — the message-type half of that card.
Prose only, one file:
content/docs/protocol/kernel/realtime-protocol.mdx. No schema, no enum, no behaviour;packages/spec/src/api/websocket.zod.tsis byte-unchanged and was read-only for this round.The fork, and the branch this PR takes
The card offers two readings: (a) the page documents a protocol the platform does not implement, or (b) the four types are real and the declaration is missing them. Every measurement below points at (a), for all of them, individually. Nothing here adds a member to a published enum.
Prerequisite readings — taken on
origin/mainat88a933088e, anchored by contentWebSocketMessageTypestill declares exactly ten members. Read live off the built package rather than inherited from the card:WebSocketMessageType.optionsoverpackages/spec/dist/api/index.mjsprints["subscribe","unsubscribe","event","ping","pong","ack","error","presence","cursor","edit"].BaseWebSocketMessagestill typestypeagainst that enum — the reading the whole argument rests on.packages/spec/src/api/websocket.zod.ts:const BaseWebSocketMessage = z.object({ … type: WebSocketMessageType.describe('Message type'), … }), andWebSocketMessageSchemais az.discriminatedUnion('type', …)over exactly those ten branches. The premise stands.type-value site on the page rather than a lookup of the card's list. That census turned up five, not four: the card'sauth/auth_success/auth_error/subscribed, plusunsubscribedin the Unsubscribe section, which is the same defect in the same file and is corrected here too.Per-type verdict — each judged on its own evidence
The probe is one regex applied uniformly to all fifteen values (ten declared, five undeclared) over the tracked files of
packages,apps,examplesin a worktree with no build output in it:(type\s*[:=]+\s*|case\s+)['"]X['"].authauth_successauth_errorsubscribedunsubscribedThe lit control, and it lights for all ten: under the same probe every declared member returns a non-zero count —
subscribe2,unsubscribe1,event2,ping3,pong2,ack2,error9,presence1,cursor1,edit1. An instrument that returns zero for everything proves nothing; this one separates the two sets cleanly, ten from five.Two supporting readings, neither load-bearing on its own:
content/docsreturns one line forauth_success|auth_error, and it is not a hit:packages/spec/src/identity/protocol.ts:136isOAUTH_ERROR: 'oauth_error', an OAuth error-code constant matched as a substring. Its control,ErrorMessageSchemaon the same corpus, returns 13 occurrences across 6 files.objectuicheckout returns 0 for all five — but its control does not light there (subscribe,ping,pong,ackare all 0 too), so that repo carries no WebSocket message producer at all and those zeros carry no weight about the five specifically. Reported rather than counted.cloudis not checked out in this container, so it is not measured.The declared contract agrees with the verdict independently of any grep:
IRealtimeService.handleUpgradeis optional and unimplemented across the open framework (#2462, #3197), the module note onwebsocket.zod.tssays "no WebSocket server is mounted", andWebSocketConfigSchema.headersis described as "Custom headers for WebSocket handshake" — the declared seam for a credential is the HTTP upgrade, not an in-band message. Soauthis not an omission from the enum; authentication is declared somewhere else. Likewisesubscribed/unsubscribedare not omissions:AckMessageSchema(type: 'ack',ackMessageId,success, optionalerror) is the acknowledgement the protocol declares.The corrected prose
BaseWebSocketMessagepinstypeto them and that a message outside the enum matches no branch of the discriminated union, and carries a callout naming the five types the page used to teach and why they were not protocol.WebSocketConfig.headersintoIRealtimeService.handleUpgrade(request), and notes that a refused upgrade never becomes a WebSocket — so there is no success or failure message to wait for. The three handshake fences are gone.ackenvelope, withackMessageIdechoing the acknowledged message'smessageId.{ type: 'auth' }— in Establishing Connection, in the reconnection client, and under Security Considerations — no longer do. The reconnection client'sauthenticate()method and itstokenconstructor argument are gone with it; that example is about backoff.Verification
Instrument: re-run the same content census over the edited page and
safeParseeach distinct value against the built enum.The control is the point: the same call that reports every remaining site as declared still reports the five removed types as undeclared, so the zero above is a reading and not a dead instrument. Before the edit the same census printed five undeclared values across eight sites.
packages/specwas built under the shared verify lock (VERDICT command-exit 0) so the parse reads a freshdist/, not a stale one.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over the edited file returns nothing.A repo-wide lint run is CI's; this diff touches one
.mdxfile and no package, so the affected-package build/test closure is empty.Changeset
skip-changeset. The rule: a changeset is owed when the PR moves something published, and what a package publishes is what itsfiles[]actually ships. The measurement: of 81package.jsonfiles on this tree, 11 areprivate: trueand 70 are publishable; every one of the 70 declares an explicitfiles[](none ships its whole directory), none of those arrays mentionscontent, and none of the 70 lives at the repo root — so no published tarball can containcontent/docs/**by any route. Positive control for the reader:@objectstack/specreads back["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"], so it is reading real arrays.content/is consumed only byapps/docs, which isprivate: true.验收备注
subscription_id,object,events,filter— and those spellings survive on the remaining subscribe fences (Subscribe to Object Events, Subscribe to Specific Record, Subscribe to Query Results, Permission Enforcement). They use a declaredtype, so they are the field-shape half of [finding] realtime-protocol.mdx documents four WebSocket message types thatWebSocketMessageTypedoes not declare (auth,auth_success,auth_error,subscribed) #17184 rather than the message-type half, and rewriting all of them ontoSubscribeMessageSchema({ messageId, type, timestamp, subscription: { subscriptionId, events, objects?, filters?, channels? } }) is a materially larger edit on a surface this round was not dispatched to. Naming it here so the seat can decide whether it is a follow-up round or a second card.unsubscribedwas not in the card's list of four. It is the same defect class, in the same file, inside the declared file face of this round's claim, and it is a mechanical correction of a form already pinned by the other four — so it is corrected here rather than filed. Evidence:"type": "unsubscribed"in the Unsubscribe section; 0 probe hits;WebSocketMessageType.safeParse('unsubscribed')returnssuccess: false.🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code