Skip to content

docs(spec): name the shape the transports carry in the websocket retirement note - #16978

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-15243-websocket-retirement-note-names-carried-shape
Sep 8, 2026
Merged

docs(spec): name the shape the transports carry in the websocket retirement note#16978
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-15243-websocket-retirement-note-names-carried-shape

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #15243

Clause-②: no
This corrects one clause of a retirement comment so that it names the shape the transports
actually carry. No accept set moves, no export is added or removed, no key is retired or revived,
and no schema arm, bound or behaviour changes. SubscriptionEventSchema.filters and
EventSubscriptionSchema.filters both stay exactly as they are, and the ADR-0104 tombstone
paragraph is untouched. The dispatch's fence — stop and report if correcting the sentence appears
to require touching a key, a type, an export or a tombstone — was never reached: the diff is six
comment lines plus a changeset. 拉回已声明契约 ⇒ 常规档.

What was wrong

packages/spec/src/api/websocket.zod.ts carries the retirement note for the deleted
FilterOperator / EventFilterCondition / EventFilterSchema vocabulary. Two sentences in, it
stated, as a positive fact:

matchesSubscription matches on object name and event type only (see contracts/realtime-service.ts), and the subscription shape the transports actually carry is the separate, deliberately unvalidated filters: z.unknown() on SubscriptionEventSchema (api/realtime.zod.ts).

The first half is exact. The second half named the wrong shape — and since it was the only
prose in the repo connecting the two, it read as evidence that SubscriptionEventSchema has a
consumer. The liveness ledger seeded in PR #15242 grades every property of that schema dead on
the opposite measurement, so the tree carried a ledger row and a source comment that disagreed.

The correction

Only the sentence's second half moved:

 // object name and event type only (see `contracts/realtime-service.ts`), and the
-// subscription shape the transports actually carry is the separate, deliberately
-// unvalidated `filters: z.unknown()` on `SubscriptionEventSchema`
-// (`api/realtime.zod.ts`).
+// subscription shape the transports actually carry is a plain TS interface one
+// directory over — `contracts/realtime-service.ts#RealtimeSubscriptionOptions`,
+// whose `object` and `eventTypes` are the only two fields that predicate reads.
+// The separate, deliberately unvalidated `filters: z.unknown()` on
+// `SubscriptionEventSchema` (`api/realtime.zod.ts`) is a sibling declaration of
+// the same unenforced kind, not a shape any transport carries.

Three things this does deliberately, each one flagged by triage as likely to be simplified away:

  • The first half is untouched. matchesSubscription matches on object name and event type
    only — already exact, and left alone byte for byte.
  • SubscriptionEventSchema.filters keeps its place in the note, as the sibling declaration of
    the same unenforced kind. It is simply no longer described as a shape anything transports.
  • The anchor is a symbolcontracts/realtime-service.ts#RealtimeSubscriptionOptions — not a
    bare name and not a line number. The whole point of the sentence is which of four same-ish
    spellings is meant, so a bare name would re-plant the very ambiguity it exists to remove; and a
    path:NNN citation is what PR docs(spec,rest): anchor record-validator citations by symbol, not line #16956 spent seven prose citations repointing away from.

Quoted verbatim from triage, per the repo's rule that a Chinese ruling is not translated:

⭐ 修法里有一条要求值得强调,别简化掉:用符号锚而不是裸名(contracts/realtime-service.ts#RealtimeSubscriptionOptions)。

⛔ 只改后半句,前半句是对的 … ⭐ 并且 SubscriptionEventSchema.filters 该留在原处 —— 作为共享 z.unknown() 待遇的姊妹声明

Why the named symbol is the right one

Four same-ish spellings exist; exactly one is executed.

spelling where executed?
SubscriptionSchema / SubscriptionEventSchema api/realtime.zod.ts no
EventSubscriptionSchema api/websocket.zod.ts no
RealtimeSubscriptionOptions contracts/realtime-service.ts yes — only this one
Subscription (file-local interface) service-realtime/src/in-memory-realtime-adapter.ts unrelated to the spec symbol of that name

Re-measured on this branch's own base ee4a59b6c, not inherited from the card's 5f5511f0:

  • The false clause was present verbatim.
  • RealtimeSubscriptionOptions is declared at packages/spec/src/contracts/realtime-service.ts:38
    and consumed at :109. It is the only declaration site of that symbol in the repo — one hit —
    so the new anchor resolves unambiguously.
  • matchesSubscription reads sub.options (typed RealtimeSubscriptionOptions) and compares
    opts.object and opts.eventTypes, and nothing else. Hence "the only two fields that predicate
    reads" — deliberately not "the only two fields on the interface", since the interface also
    declares a filter? key that the adapter never evaluates.

The zero, with a firing control. A bare zero is not a reading, so both halves were re-run:

instrument SubscriptionEventSchema RealtimeSubscriptionOptions (control)
packages/ minus packages/spec, excluding CHANGELOGs 0 files 3 files
everything outside packages/spec, excluding CHANGELOGs 3 files 4 files

The control fires in both rows, so the zero is a measured absence rather than instrument silence.
The three extra hits in the wider row are content/docs/references/api/realtime.mdx (generated
reference), skills/objectstack-api/references/_index.md (published prose) and
docs/qa/platform-checklist/coverage.json (a waiver string) — no transport among them. The single
import statement naming SubscriptionEventSchema anywhere outside packages/spec lives in that
generated .mdx. The card's claim stands: no transport parses a SubscriptionEvent.

Changeset — both halves measured, not assumed

.changeset/websocket-retirement-note-carried-shape.md, @objectstack/spec: patch.

  1. Is the path inside files[]? Yes — websocket.zod.ts matches the src/**/*.zod.ts entry.
  2. Is the changed text in what the package publishes? Yes, measured on the real npm pack
    tarball (2073 files) with controls that actually discriminate:
needle src dist json-schema tarball total
the new // comment (subject) 1 0 0 1
a .describe() from the same file (positive control) 1 2 4 7
a string unique to src/api/websocket.test.ts (negative control) 0 0 0 0

The positive control lands in dist and json-schema, so those zeros for the subject are a
measured absence, not a silent instrument; the negative control confirms the tarball genuinely
excludes what files[] excludes. The tree that decides it is src/**/*.zod.ts — the source
file ships to npm verbatim, comments included. So something published does move, and
skip-changeset would have been wrong.

Checks

Derived from the tree rather than recalled, via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, then reconciled with
--ran: 68 of 69 derived gate families run and green.

check result
pnpm --filter @objectstack/spec build pass (verdict command-exit 0)
pnpm --filter @objectstack/spec test pass — 467 files, 13100 tests
pnpm --filter @objectstack/spec typecheck pass (incl. check:scripts-typecheck, check:test-typecheck)
pnpm lint (full repo, eslint . --no-inline-config) pass — exit 0, zero bytes of output, re-run at final head fdb725a6b
pnpm --filter @objectstack/spec check:generated all 15 generated artifacts up to date
pnpm check:nul-bytes + a manual control-byte scan of both files pass
the other 63 derived families pass
pnpm check:dual-build-cjs-loads NOT MEASURED — see below

Does this comment reach a generated page? No. check:generated reports all fifteen artifacts
current, check:docs (content/docs/references/**) among them, so no docs build or regeneration
is owed. That is consistent with the tarball reading: a // line comment is not a .describe()
string and does not travel to the reference tree.

The one gate not measured. pnpm check:dual-build-cjs-loads exited 3, its own
PREREQUISITE NOT MET code, printing "⛔ This is NOT a pass: nothing was measured" and listing 87
packages with no dist/. It wants a full pnpm build of the farm, which is CI's run, not a
comment-only PR's. It is recorded as NOT MEASURED — not as a red, and not inside the green count.
A second gate, check:doc-formula-expressions, exited 3 the same way; that one needed only
@objectstack/formula and @objectstack/lint built, so it was cleared and re-run green.

No ablation or reverse-verification run is reported, because there is none to run honestly: this
change adds no guard and alters no executed path, so there is no assertion whose failure could be
demonstrated. Report of absence is not being offered as success anywhere above.

验收备注

Observed while measuring, out of this card's declared file surface, not filed and not fixed
for the PM to route:

  • packages/spec/liveness/realtime_subscription.json (_note) quotes the very clause this PR
    rewrites — "the prose in websocket.zod.ts:62-63 saying …" — as the warning that patches over
    the source comment. After this PR that quoted prose no longer exists in the tree, so the ledger
    note becomes a correction of something already corrected. Two things worth stating precisely:
    its path:NNN anchor already pointed at the sentence's first half rather than the second
    half it quotes, measured at ee4a59b6c before this change; and this PR does not move lines
    62-63 at all, since the edit begins at line 64. So the drift is pre-existing, not introduced
    here. check:liveness is green either way — the gate does not read the quotation.
  • Leaving it alone was deliberate. It is a hand-authored ledger, it is declared ≠ enforced (ADR-0049): RealtimeEventType spells record.* while the runtime emits data.record.*, and SubscriptionSchema.filters is z.unknown() nothing reads #14446's artifact, and
    editing it would pull check:liveness in as a verification surface this card's file fence does
    not cover. The card, its triage and the claim all name websocket.zod.ts as the only file
    involved.

Generated by Claude Code

…rement note

The retirement note for the deleted `FilterOperator` / `EventFilterCondition` /
`EventFilterSchema` vocabulary said the subscription shape the transports
actually carry is the `filters: z.unknown()` on `SubscriptionEventSchema`
(`api/realtime.zod.ts`). Measured, no transport parses that schema at all: the
shipped subscription path carries a plain TS interface one directory over,
`contracts/realtime-service.ts#RealtimeSubscriptionOptions`, whose `object` and
`eventTypes` are the only two fields `matchesSubscription` reads.

Only the sentence's second half moves. Its first half — `matchesSubscription`
matches on object name and event type only — was already exact.
`SubscriptionEventSchema.filters` keeps its place in the note as the sibling
declaration of the same unenforced kind, which is what it is; it is simply no
longer described as a shape anything transports. The correction uses a symbol
anchor rather than a bare name, because the whole point of the sentence is which
of four same-ish spellings is meant, and rather than a line number, because
prose citations by line rot.

No key, type, export or tombstone is touched, and no behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…t-note correction

Both halves of the publish test say yes, so this is not a `skip-changeset`: the
changed path matches `src/**/*.zod.ts` in the package's `files[]`, and the
changed text is byte-present in the packed tarball. Measured on the real
`npm pack` output with controls — a `.describe()` from the same file reaches
`src` + `dist` + `json-schema` (7 files), a string unique to a sibling
`*.test.ts` reaches none, and this `//` comment reaches exactly the shipped
source file. `src/**/*.zod.ts` is the tree that decides it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/api/websocket.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/api/websocket.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 50dc2141aed8d6b99e42f3a47dcd80892f133ac6packageMentionDocs.

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/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

websocket.zod.ts's retirement note claims the transports carry SubscriptionEventSchema; measured, they carry RealtimeSubscriptionOptions

2 participants