Skip to content

[finding] realtime-protocol.mdx documents four WebSocket message types that WebSocketMessageType does not declare (auth, auth_success, auth_error, subscribed) #17184

Description

@baozhoutao

Found while working on #16935 (flattening the type: "error" fences on the same page onto ErrorMessageSchema). That card's dispatch deliberately fenced the type: "auth_error" fence at :220 out of scope and asked for it to be measured and reported rather than rewritten — measuring it turned up a wider, separate defect, filed here.

Measured on origin/main at fd5cff209f. Every line number below is from that tree.

The declared contract

packages/spec/src/api/websocket.zod.ts:38 declares the closed vocabulary of WebSocket message types, and every message schema on that tree pins its type to a member of it:

export const WebSocketMessageType = z.enum([
  'subscribe', 'unsubscribe', 'event', 'ping', 'pong',
  'ack', 'error', 'presence', 'cursor', 'edit',
]);

BaseWebSocketMessage (:269) types its type field as that enum, so a message whose type is not a member of it does not parse against any schema in the file.

What the page teaches instead

content/docs/protocol/kernel/realtime-protocol.mdx documents a connection handshake and a subscribe acknowledgement built entirely out of message types that are not in that enum:

site type in the fence declared in WebSocketMessageType?
:202 (JSON fence) auth no
:210 (JSON fence) auth_success no
:220 (JSON fence) auth_error no
:258 (JSON fence) subscribed no
:176, :590, :1012 (JS snippets) auth no

The same fences also carry top-level fields no schema declares — token, user_id, session_id, expires_at on the auth pair, and subscription_id / object / events / filter on the subscribe pair.

Read live against the built spec, so this is not a grep artefact:

error          DECLARED
auth_error     NOT DECLARED
auth           NOT DECLARED
auth_success   NOT DECLARED
subscribed     NOT DECLARED

(WebSocketMessageType.safeParse(t) over packages/spec/dist/api/index.mjs; error is the positive control on the same corpus and the same call.)

Producer readings on the same tree, each with a positive control:

reading result positive control
auth_error in packages/** 1 line, and it is not a hit: packages/spec/src/identity/protocol.ts:136 is OAUTH_ERROR: 'oauth_error', an OAuth error-code constant matched as a substring ErrorMessageSchema in packages/** returns 11 lines across 6 files
auth_success in the repo, excluding content/docs 0 same control as above

Why this is a defect and not a style note

Nothing on this tree emits any WebSocket message at all — packages/spec/CHANGELOG.md records that "no WebSocket server is mounted (#2462); the protocol is a future wire contract", and the page's own implementation-status callout says the transport is unwired. So the page is the only authoring surface for this protocol, and WebSocketMessageType is the only declaration of it. A client written from the handshake section sends {"type": "auth", ...}, which the one schema governing that field rejects; a client written from the subscribe section waits for {"type": "subscribed"}, which nothing can ever send. Copying the examples fails against the declared contract.

Two readings, and they lead to opposite edits

Unlike #16935's envelope half — where both readings agreed, because ErrorMessageSchema is flat whether or not the capability ever ships — this one genuinely does not settle itself:

  • A — the page is wrong. The handshake is not part of the declared protocol; rewrite or remove those fences, and express authentication in terms the enum can carry.
  • B — the enum is incomplete. An auth handshake and a subscribe acknowledgement are real parts of a WebSocket wire protocol that the spec simply has not declared yet; the enum and the message schemas should grow auth / auth_success / auth_error / subscribed before the transport lands.

B is a protocol change and needs a decision — which is exactly why #16935's dispatch ruled :220 out of that card's scope: rewriting the fence to type: "error" there would have been changing the protocol under cover of a documentation fix.

Not fixed here

Left byte-unchanged by the #16935 PR. Nothing gates it either: these are JSON fences, so no type-check compares a documented realtime message against the declared enum — that gap is the class (B) population of #17127, whose census should count this page.

Related: #16935, #17127, #16937.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions