Skip to content

docs(protocol): drop the five WebSocket message types WebSocketMessageType does not declare - #17700

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17184-realtime-protocol-message-types
Sep 11, 2026
Merged

docs(protocol): drop the five WebSocket message types WebSocketMessageType does not declare#17700
os-bill merged 1 commit into
mainfrom
claude/issue-17184-realtime-protocol-message-types

Conversation

@os-bill

@os-bill os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Part of #17184 — the message-type half of that card.

  • Clause-②: no

Prose only, one file: content/docs/protocol/kernel/realtime-protocol.mdx. No schema, no enum, no behaviour; packages/spec/src/api/websocket.zod.ts is 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/main at 88a933088e, anchored by content

  1. WebSocketMessageType still declares exactly ten members. Read live off the built package rather than inherited from the card: WebSocketMessageType.options over packages/spec/dist/api/index.mjs prints ["subscribe","unsubscribe","event","ping","pong","ack","error","presence","cursor","edit"].
  2. BaseWebSocketMessage still types type against 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'), … }), and WebSocketMessageSchema is a z.discriminatedUnion('type', …) over exactly those ten branches. The premise stands.
  3. The page still taught all four — anchored by content, not by line, with a census of every type-value site on the page rather than a lookup of the card's list. That census turned up five, not four: the card's auth / auth_success / auth_error / subscribed, plus unsubscribed in the Unsubscribe section, which is the same defect in the same file and is corrected here too.
  4. Producer/consumer search, per type, with a lit control — below.

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, examples in a worktree with no build output in it: (type\s*[:=]+\s*|case\s+)['"]X['"].

type declared? probe hits verdict
auth no 0 documentation only ⇒ fork (a)
auth_success no 0 documentation only ⇒ fork (a)
auth_error no 0 documentation only ⇒ fork (a)
subscribed no 0 documentation only ⇒ fork (a)
unsubscribed no 0 documentation only ⇒ fork (a)

The lit control, and it lights for all ten: under the same probe every declared member returns a non-zero count — subscribe 2, unsubscribe 1, event 2, ping 3, pong 2, ack 2, error 9, presence 1, cursor 1, edit 1. 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:

  • A broader whole-repo probe outside content/docs returns one line for auth_success|auth_error, 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. Its control, ErrorMessageSchema on the same corpus, returns 13 occurrences across 6 files.
  • The sibling objectui checkout returns 0 for all five — but its control does not light there (subscribe, ping, pong, ack are 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. cloud is not checked out in this container, so it is not measured.

The declared contract agrees with the verdict independently of any grep: IRealtimeService.handleUpgrade is optional and unimplemented across the open framework (#2462, #3197), the module note on websocket.zod.ts says "no WebSocket server is mounted", and WebSocketConfigSchema.headers is described as "Custom headers for WebSocket handshake" — the declared seam for a credential is the HTTP upgrade, not an in-band message. So auth is not an omission from the enum; authentication is declared somewhere else. Likewise subscribed / unsubscribed are not omissions: AckMessageSchema (type: 'ack', ackMessageId, success, optional error) is the acknowledgement the protocol declares.

The corrected prose

  • A new Declared message vocabulary section names the ten members, states that BaseWebSocketMessage pins type to 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.
  • Authentication now says the declared protocol has no authentication message, puts the credential on the upgrade request via WebSocketConfig.headers into IRealtimeService.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.
  • The Subscribe and Unsubscribe acknowledgements are now the declared ack envelope, with ackMessageId echoing the acknowledged message's messageId.
  • The three JavaScript snippets that sent { type: 'auth' } — in Establishing Connection, in the reconnection client, and under Security Considerations — no longer do. The reconnection client's authenticate() method and its token constructor argument are gone with it; that example is about backoff.
  • ⛔ Nothing was written implying the five types are coming. The page states the declared position only.

Verification

Instrument: re-run the same content census over the edited page and safeParse each distinct value against the built enum.

ack  DECLARED   error DECLARED   event DECLARED   ping DECLARED
pong DECLARED   subscribe DECLARED   unsubscribe DECLARED
sites=22 distinct=7 undeclared=0
control (the five removed types, same call): auth=NOT DECLARED auth_success=NOT DECLARED
auth_error=NOT DECLARED subscribed=NOT DECLARED unsubscribed=NOT DECLARED

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/spec was built under the shared verify lock (VERDICT command-exit 0) so the parse reads a fresh dist/, 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 .mdx file 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 its files[] actually ships. The measurement: of 81 package.json files on this tree, 11 are private: true and 70 are publishable; every one of the 70 declares an explicit files[] (none ships its whole directory), none of those arrays mentions content, and none of the 70 lives at the repo root — so no published tarball can contain content/docs/** by any route. Positive control for the reader: @objectstack/spec reads 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 by apps/docs, which is private: true.

验收备注

  • What is deliberately left standing. The card also names top-level fields no schema declares on the subscribe pair — 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 declared type, so they are the field-shape half of [finding] realtime-protocol.mdx documents four WebSocket message types that WebSocketMessageType does not declare (auth, auth_success, auth_error, subscribed) #17184 rather than the message-type half, and rewriting all of them onto SubscribeMessageSchema ({ 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.
  • A fifth type, corrected in place. unsubscribed was 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') returns success: false.
  • No new card was opened by this round.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH


Generated by Claude Code

…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
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 11, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 11, 2026
@os-bill
os-bill marked this pull request as ready for review September 11, 2026 16:24
@os-bill
os-bill added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 4273a97 Sep 11, 2026
37 checks passed
@os-bill
os-bill deleted the claude/issue-17184-realtime-protocol-message-types branch September 11, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants