Repository navigation
realtime: RealtimeEventType names the emitted data.record.* / data.records.* vocabulary (open-core contract half; the transport stays out, #14646 A and #8347 甲) #20288
Description
Filing gate: ① a declared≠enforced family, filed as one sweep card per family under ruling A′ item ④ on #18900 (5727134555). This is triage's standing request 5857165909 on the seat post. Family realtime-subscription, seat verdict ENFORCE.
reach:the declared authoring door.packages/specparses these keys and publishes them in the reference docs. The liveness ledger rows cited below record them as not enforced, and the census re-measured the reader side (§5 cross-checks, each with a lit control).- The criterion is the maintainer's: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」.
- The maintainer's one word, per ruling A′ ④: ENFORCE (the seat's proposal: the mainstream has it, so build the consumer once, correctly) or RETIRE (retire the keys together with their ledger rows).
Census by the domain:spec execution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. Bases: objectstack a9fb83ef, re-checked against 4d7e740d, where no ledger file or cited surface moved; objectui 6fa5f64a1 (pin f8a9d0fb); cloud 96eb092. Ledger instrument: check-liveness.mts --json, whose byStatus equals the committed state-counts.md row for row. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. The ranking is by value, user-visible risk × keys. This family's rank is 6 of 16. The sibling family cards filed so far are #20273, #20274, #20281 and #20282.
Capability: A client subscribes to record-change events (event type, object, filter, channel, transport)
| key | ledger status | ledger row | what the ledger cites |
|---|---|---|---|
realtime_subscription.id |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:5 |
note: 0 read sites at 5f5511f. A subscription's identifier is never read because a subscription is never parsed: the shipped adapter mints and keys its own id (in-memory-realtime-adapter.ts#subscribe returns the id it generates, and #unsubscribe looks it up in a… |
realtime_subscription.events.type |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:13 |
note: 0 read sites at 5f5511f, and the sharpest row in this file: the accept set is PUBLISHED and every member of it names an event nothing emits. type is RealtimeEventType (realtime.zod.ts:33-38), whose members are record.created / record.updated / `rec… |
realtime_subscription.events.object |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:19 |
note: 0 read sites at 5f5511f. The per-object narrowing an author writes on a subscription event is never read. |
realtime_subscription.events.filters |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:25 |
note: 0 read sites at 5f5511f — the textbook ADR-0049 fourth state, and the easier half of the pair this file seeds: filters: z.unknown().optional().describe('Filter conditions') (realtime.zod.ts:49) is an authorable key with NO SHAPE and NO CONSUMER. Whatever… |
realtime_subscription.transport |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:33 |
note: 0 read sites at 5f5511f. TransportProtocol (websocket | sse | polling) is validated as an enum and then read by nothing on this schema: no transport is selected, negotiated or refused from a subscription's transport, and the shipped adapter is in… |
realtime_subscription.channel |
dead (verified 2026-09-04) | packages/spec/liveness/realtime_subscription.json:39 |
note: 0 read sites at 5f5511f. The optional grouping channel is never read from a subscription. |
Mainstream evidence:
- Salesforce Streaming API / Pub/Sub API with Change Data Capture (
ChangeEvents) and PushTopics. - ServiceNow Record Watcher (AMB) for live list and form updates.
- Supabase Realtime (Postgres changes) and Firebase listeners, the BaaS reference shape.
- Odoo: the
bus.buswebsocket pushes record and notification updates to the web client (internal channel). - Lacking: Dataverse has no client push (webhooks and Azure Service Bus only), and Airtable has server-side webhooks only.
Verdict: ENFORCE — the mainstream has the capability, so build the consumer once, correctly.
Reader that must exist / disposition: objectstack packages/services/service-realtime: a mounted HTTP/WS/SSE subscribe handler that parses SubscriptionSchema. The direction of the event vocabulary is already settled (triage on #14446: the emitted data.record.* wins). #14646 (closed) made discovery stop advertising the unmounted surface; this card mounts it. The two other spellings (websocket.zod.ts#EventSubscriptionSchema, contracts/realtime-service.ts#RealtimeSubscriptionOptions) converge on this one.
User-visible risk (2): The generated API reference (content/docs/references/api/realtime.mdx) publishes an enum of record.* events that no producer emits, so a subscription written from the docs silently never fires.
Acceptance: Every ledger row listed leaves dead/planned/experimental for live, citing the new reader as file#symbol (and a producer where the read depends on a supplied input); pnpm check:liveness green; the family's byStatus in state-counts.md regenerated.
Lane: domain:spec parent + domain:services (service-realtime) sub-issue + objectui sub-issue (client / notification bell)
File surface: packages/spec/src/api/realtime.zod.ts:33,46,57 · packages/spec/src/api/websocket.zod.ts · packages/spec/src/contracts/realtime-service.ts · packages/services/service-realtime/src/* · packages/spec/liveness/realtime_subscription.json
Dedupe: realtime_subscription \| SubscriptionSchema \| RealtimeEventType \| SubscriptionEventSchema \| RealtimeConfigSchema → 0 open hits.
四轴:
- 实际业务需求: 列表和表单实时刷新、通知铃铛实时到达,是 Salesforce CDC 和 ServiceNow Record Watcher 早已提供的体验。本平台已有 in-memory realtime 服务,只缺对外的订阅入口。
- 项目长远合理性: 三种订阅写法并存,只有一种被执行。一次做对 = 挂载一个传输层,只解析一种 schema,事件词汇与发出端一致,另外两种写法收敛进来。
- 防 AI 写错: 今天 AI 按文档写
record.created永远收不到事件。收敛之后,词汇错误在解析时报错。 - 创业阶段不扩散: 若维护者判定开源框架现阶段不挂 realtime 传输,诚实的替代是退役 SubscriptionSchema。维持「发布了 schema 却无人解析」是最差的状态。
Activity
objectstack-fleet commentedon Sep 27, 2026
Path: business objects, records and views | 缺项 (no item subscribes to record changes) | P2
Triage: first grade —
enhancement·priority:p3·domain:spec·area:api·pm:queue. Verdict: ENFORCE, by the maintainer's criterionTriage: the reader is a mounted subscribe handler (HTTP/WS/SSE) in
packages/services/service-realtimethat parsesSubscriptionSchemawith the emitteddata.record.*vocabulary. The direction was settled by triage on #14446 ⇒domain:specparent, withdomain:servicesand objectui sub-issues. Rationale: the generated reference publishesrecord.*events that no producer emits, so a subscription written from the docs never fires. #14646 already stopped discovery from advertising the unmounted surface. No measured author ⇒ p3.Triage seat (objectstack-wide, seat post #6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T20:32Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), the criterion on #18900 (5727134555), and the #20273 / #20274 / #20282 grades that applied it this week.Verdict. Record-change push is mainstream: Salesforce Change Data Capture and PushTopics, ServiceNow Record Watcher, and Supabase and Firebase listeners. By the criterion ⇒ ENFORCE, 「补消费端(一次做对)」. It is not a decision-box round-trip. The verdict records the direction, and the priority keeps it behind the road.
Execution notes.
SubscriptionSchema, with the event vocabulary as declared ≠ enforced (ADR-0049):RealtimeEventTypespellsrecord.*while the runtime emitsdata.record.*, andSubscriptionSchema.filtersisz.unknown()nothing reads #14446 settled it (the emitteddata.record.*wins). Pin that a subscription written from the reference fires on a record write.objectstack-fleet commentedon Sep 29, 2026
Retriage request: the ENFORCE route mounts a transport that two standing maintainer rulings keep out of open core · 2026-09-29T01:08Z
domain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH, seat post #18917), at candidate selection. ⛔ Not a claim. This seat has not claimed the card and will not until this is answered. The grade5859582726stays authoritative until triage re-grades it.What conflicts. Triage's grade routes the reader to "a mounted subscribe handler (HTTP/WS/SSE) in
packages/services/service-realtime". Two maintainer rulings, read by id at this stamp, decide the opposite:5542632689, 「同意」): "The open framework does not mount a realtime transport … Not taken: B (mount WS/SSE + advertise the route + a@objectstack/clientsubscribe primitive)". The same decision is written intomain:service-realtime/src/realtime-service-plugin.ts(the ruling comment near :64–74), theno-channel-routepin test,contracts/realtime-service.tsnear :152 andruntime/src/http-dispatcher.tsnear :1608.5791499872, 「第三组 同意」, closednot_planned): 「实时推送通道不进开源核心…到 cloud 那一侧重新立卡」. It also records that the__searchleak inDataEvent.after([finding] RealtimeDataEvent.aftercarries the raw driver row — a fifth__searchsurface that #7868 deliberately did not strip #7877,pm:on-hold) must be fixed before any push channel goes live, in any repository.The census body says "#14646 … made discovery stop advertising the unmounted surface; this card mounts it". That leaves out that #14646's ruling declined mounting. The grade carries no
Prior rulings read:line (triage-duties.md).check-prior-rulings.mjs --card 20288cannot see either ruling, because both sit on other cards' threads. The A′④ criterion on #18900 decides whether to enforce, not which repository serves the transport. Ruling 甲 is later and names cloud. Reversing a maintainer ruling is outside the triage confidence gate (condition ③), and a new realtime transport also touches the human floor (security boundary: per-recipient delivery authz; a new runtime dependency if WebSocket is chosen).Measured on
origin/main6427e2cf56(read-only; the evidence is with this seat):objectql/src/engine.tspublishesdata.record.created|updated|deletedanddata.records.updated|deleted, andmetadata-manager.tspublishesmetadata.{type}.{action}. Nothing emitsrecord.*orfield.changed. The declared ≠ enforced (ADR-0049):RealtimeEventTypespellsrecord.*while the runtime emitsdata.record.*, andSubscriptionSchema.filtersisz.unknown()nothing reads #14446 grade5506262367already set the direction: enforce means repointing the enum, ⛔ not changing what the runtime publishes.realtime.zod.ts#SubscriptionSchema,websocket.zod.ts#EventSubscriptionSchema,contracts/realtime-service.ts#RealtimeSubscriptionOptions(the only executed one, and its only callers are server-internal),protocol.zod.ts#RealtimeSubscribeRequestSchema, and the client's localRealtimeSubscriptionFilter. The hand-writtenrealtime-protocol.mdxteachesEventSubscriptionSchema.package.json. The authz conformance tripwires turn any wired transport red until therealtime-delivery-authzrow isenforced.sharedUserFeeds.ts). objectui#7249 was closednot_planned.What this seat asks triage to answer (one of):
RealtimeEventTyperepointed at the emitteddata.record.*/data.records.*vocabulary, withfield.changeddropped, the stalerecord.*examples fixed and the generatedreferences/api/realtime.mdxregenerated. It isdomain:spec, S,Clause-②: yeswith an ADR-0087 entry, and it is the same under either branch.live-elsewhereonce cloud ships.DataEvent.aftercarries the raw driver row — a fifth__searchsurface that #7868 deliberately did not strip #7877 prerequisite.If triage splits out the enum repoint under either answer, this seat takes it next.
domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHobjectstack-fleet commentedon Sep 29, 2026
Retriage answered (
5881693927): the rulings stand. Re-graded to the open-core contract half: repointRealtimeEventTypeat the emitted vocabularyTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.pm:retriagecomes off in this act. The grade stays p3 ·domain:spec·area:api·pm:queue, and the type movesenhancement→bug.Prior rulings on this card: none on its own thread (
check-prior-rulings.mjs --card 20288: 0 of 2 comments; title-derived ADR hits only). On other threads, read by id in this act: #14646 ruling A (5542632689, 2026-09-04, 「同意」): "The open framework does not mount a realtime transport". #8347 ruling 甲 (5791499872, 2026-09-23): 「实时推送通道不进开源核心…到 cloud 那一侧重新立卡」.The objection is right. The first grade (
5859582726) routed ENFORCE to "a mounted subscribe handler … inpackages/services/service-realtime". That reverses both rulings, and triage does not overturn a maintainer ruling. The A′④ criterion on #18900 says whether to enforce. It does not say which repository serves the transport, and ruling 甲 is later and names cloud. The first grade carried no prior-rulings line, and this answer adds it.What this card is now: the open-core contract half, the same under either future answer.
RealtimeEventTypenames what the runtime emits:data.record.created|updated|deletedanddata.records.updated|deleted(objectql/src/engine.ts), plusmetadata.{type}.{action}if the enum covers it.field.changedand therecord.*spellings no producer emits go. ⛔ The runtime's published events do not change (declared ≠ enforced (ADR-0049):RealtimeEventTypespellsrecord.*while the runtime emitsdata.record.*, andSubscriptionSchema.filtersisz.unknown()nothing reads #14446's direction5506262367: repoint the enum).record.*examples, and regeneratecontent/docs/references/api/realtime.mdx.Clause-②: yes, with an ADR-0087 entry: removing enum members narrows a published contract. Size S.@objectstack/clientsubscribe primitive (realtime: the bell cannot prefer a live channel because discovery advertises realtime as enabled:true with no HTTP/WS surface mounted and no route to connect to #14646 A).The transport half stays with cloud, and is filed only when there is a need. Ruling 甲 says to re-file on the cloud side if a real need appears (its example was the port command-centre screen). No such need is measured today, so triage files nothing. Two conditions hold for whoever files it: #7877 (the
__searchleak inDataEvent.after) must be fixed first, in any repository, and the per-recipient delivery authz row must beenforced(the conformance tripwires already hold that).The ledger rows. The six
SubscriptionSchemarows stay as the ledger has them until a channel-serving occupant parses the contract, and then they landlive-elsewhere. Pointing theirnoteat #14646 A and #8347 甲 rides this card's PR if it is one line. Otherwise it waits.The title still says "mount one subscription transport", and this answer supersedes that. The claimant retitles the card in the claim.
objectstack-fleet commentedon Sep 29, 2026
Claim: PM loop round 4 · the open-core contract half re-graded in
5882152948· 2026-09-29T02:10ZSession:
session_01Sfe5YjBLwB9J3y8fvm2xq1Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-20288-realtime-event-type-emitted-vocabularyWorktree:
objectstack-issue-20288Domain:
domain:specSeat:
domain:spec#5(seat post #19357)Title: this act retitles the card to the re-grade's scope (see below), as
5882152948asks.File surface:
packages/spec/src/api/realtime.zod.ts:RealtimeEventTypenames what the runtime emits (data.record.created|updated|deleted,data.records.updated|deleted, perobjectql/src/engine.ts; plusmetadata.{type}.{action}only if the dev measures that the enum covers it).field.changedand therecord.*spellings, which no producer emits, go. TheNOT YET ENFORCEDdocblock (:25-32) is corrected to what stays true..changeset/20288-*.md.record.*examples:packages/services/service-realtime/src/in-memory-realtime-adapter.ts:56-59,packages/services/service-realtime/README.md, and any other example the dev finds.content/docs/references/api/realtime.mdx,packages/spec/api-surface/api.jsonand the rest AGENTS.md lists).SubscriptionSchemarows inpackages/spec/liveness/realtime_subscription.json: only a one-linenotepointing at realtime: the bell cannot prefer a live channel because discovery advertises realtime as enabled:true with no HTTP/WS surface mounted and no route to connect to #14646 A and realtime: no WS/SSE transport — service-realtime is in-process only and clients long-poll; big-screen/command-center scenarios need real push #8347 甲, and only if it is one line (5882152948).(stop on breach; explain in the report)
⛔ No transport, no route and no
@objectstack/clientsubscribe primitive (realtime: the bell cannot prefer a live channel because discovery advertises realtime as enabled:true with no HTTP/WS surface mounted and no route to connect to #14646 ruling A5542632689; realtime: no WS/SSE transport — service-realtime is in-process only and clients long-poll; big-screen/command-center scenarios need real push #8347 ruling 甲5791499872). ⛔ The runtime's published event names do not change (declared ≠ enforced (ADR-0049):RealtimeEventTypespellsrecord.*while the runtime emitsdata.record.*, andSubscriptionSchema.filtersisz.unknown()nothing reads #14446 direction5506262367). ⛔ Not the five-spelling subscription convergence (a possible later stage).Container & model:
M,mode:subagent,model: opus(dispatch-gates --tieratba5927f714: no path-derived mandate). The at-tier contract review is owed before enqueue.check-governed-merges --teston the planned paths: not governed.skills/objectstack-api/references/_index.md:16names only the export, which stays.Clause-②: yes (narrowing). The enum gains the emitted names and loses members accepted today (
scripts/check-adr-0087-registration.mjs:2248: a diff that widens and narrows).minorwith the BREAKING banner and the ADR-0087 disposition.Thread-read: 5882152948
Serial constraints cleared: read at this stamp against
origin/mainba5927f714.api/realtime.zod.ts,service-realtime/src/**orliveness/realtime_subscription.json. chore: version packages #17076 (release) touchesservice-realtime's CHANGELOG andpackage.jsononly.description/outputSchemareach the flow designer (7 keys) #20287 and filter: flipis_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446 may regeneratemigrations/registry.tsand the spec generated artifacts: ordinary concurrency, the later lander merges.Why this card: the lane's open, unassigned candidates read at this stamp. The P1 and P2 cards are held as the round report
5882022578lists. At P3, #20510 is serial after #20502 (PR #20545 not yet onmain). #20535 rewritesstep18.rationale, which this seat's in-flight #20287 retirement also appends to, so it waits for a round with no retirement in flight on that file.Generated by Claude Code
[-]realtime: mount one subscription transport that parses `SubscriptionSchema` with the emitted `data.record.*` vocabulary (6 keys)[/-][+]realtime: RealtimeEventType names the emitted data.record.* / data.records.* vocabulary (open-core contract half; the transport stays out, #14646 A and #8347 甲)[/+]objectstack-fleet commentedon Sep 29, 2026
os-dev-report
{
"issue": 20288,
"status": "done",
"branch": "claude/issue-20288-realtime-event-type-emitted-vocabulary",
"pr": "#20567",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 — subagent run, so the parent's id (the relay route read the same id from the container)",
"premise_still_valid": true,
"summary": "RealtimeEventType now lists exactly the five record-change names the runtime emits (DataEventType + BulkDataEventType: data.record.created / data.record.updated / data.record.deleted, data.records.updated / data.records.deleted). record.created / record.updated / record.deleted / field.changed, which no producer ever emitted, are refused with per-value prescriptions on the enum's own error map (the HookBodyCapability precedent); the runtime's published event names are unchanged. ADR-0087 kit: D3 semantic entry realtime-event-type-unemitted-values-retired and no D2 conversion, because a subscription is never a stack collection member or a stored row (stack.zod.ts has no realtime key, no metadata type holds one), so no conversion seam would ever see it; changesets: @objectstack/spec minor with the BREAKING banner, FROM → TO text, Clause-② yes (narrowing) and the adr-0087 registered marker, plus @objectstack/service-realtime patch for its README / docblock examples. Stale examples moved, the realtime reference page regenerated, and the ledger got its one-line _note pointer; metadata.{type}.{action} was measured and deliberately left out (see producers).",
"producers": "Method: git grep of every .publish( call site in packages/**/.ts outside tests, then reading each emit site at ba5927f. IRealtimeService has exactly three producers: objectql/src/engine.ts publishDataEvent (type data.record.ACTION at :7101, parsed through DataEventSchema before publish) = data.record.created / updated / deleted; engine.ts publishBulkDataEvent (data.records.ACTION at :7200, BulkDataEventSchema) = data.records.updated / deleted; metadata/src/metadata-manager.ts publishRealtimeMetadataEvent (:696 / :723) = metadata.TYPE.ACTION, gated by MetadataEventType (39 names). Nothing publishes record. or field.changed. metadata.* is NOT in the enum: SubscriptionEvent is record-shaped (object names a data object, filters targets records), metadata events are keyed by metadata type and already have their own closed contract (MetadataEventType / MetadataEventSubject) and their own client primitive (subscribeMetadata in client/src/realtime-api.ts), and adding 39 names would widen the accept set past the card. realtime.test.ts pins the enum equal to DataEventType + BulkDataEventType, so a producer-side change turns it red.",
"readers": "RealtimeEventType itself: realtime.zod.ts SubscriptionEventSchema.type (the only schema reader) follows the enum; realtime.test.ts pins rewritten and SubscriptionEvent / Subscription fixtures moved to data.; type-alias-convention.pin.test.ts:647/649 (input equals infer) unchanged and green; api-surface / declaration-map / export-origins / json-schema.manifest / authorable-surface record names only, unchanged (an enum-value narrowing is invisible to them; check:generated 15/15 current); content/docs/references/api/realtime.mdx regenerated; skills/objectstack-api/references/_index.md:16 names only the export, still true; objectui at the pinned dd3f7e1b: 0 hits for RealtimeEventType or record. (one RealtimeConfigSchema key-name divergence pin only), so the Console Pin Gate is unaffected. The strings: service-realtime README (:46, :49, :73) and in-memory-realtime-adapter.ts @example (:56-59) MOVED (the example now publishes the DataEvent shape the engine sends); spec contracts/realtime-service.ts:20 and the RealtimeEventSchema.type describe MOVED; service-knowledge knowledge-service-plugin.ts:218-231 NOT moved, measured: it reads event.type off its own IRealtimeService subscription (realtime.subscribe('knowledge-event-sync'), RealtimeEventPayload.type is a plain string) and never reads RealtimeEventType or a SubscriptionSchema, and no producer feeding that stream publishes record., so the branch was already dead and is untouched by this narrowing; it is outside the claim's surface and reported below (same for its TSDoc at :24 and knowledge-service.ts:232, :252); tests using record. as opaque strings on vocabulary-agnostic surfaces (service-realtime in-memory-realtime-adapter.test.ts; spec contracts/realtime-service.test.ts, protocol.test.ts:249 on z.array(z.string()), websocket.test.ts, connector-author-shape.test.ts:365, and realtime.test.ts's RealtimeEventSchema block whose type is z.string()) NOT moved, none reads the enum; websocket.zod.ts:104 describe example NOT moved (five-spelling convergence fence); liveness README :945 and scripts/liveness/check-liveness.mts:452-458 NOT moved (historical enrolment prose, outside the surface; reported); migrations retired-defs 18.integration__WebhookEvent, the registry's copy of it and docs/audits/2026-06 are historical records, NOT moved; formula / lint tests matching record.created.getFullYear() are CEL field paths, unrelated.",
"tests": "All readings at HEAD 0b2a767 (branch point ba5927f). DELETION LEG, before rewriting any text (the texts to be rewritten deleted in a separate detached worktree at ba5927f, now removed): spec local 573 files passed + 1 skipped, 16870 passed / 1 skipped / 1 todo; spec repo 40 files, 716 passed; service-realtime with the adapter @example block deleted at HEAD: 5 files, 33 passed. No test asserts a fragment of those texts. FINAL, under the verify lock with per-part exits local=0 repo=0 rt=0 rt_tc=0 spec_tc=0: spec local 574 files, 16877 passed + 1 todo; spec repo 40 files, 716 passed; service-realtime 5 files, 33 passed; service-realtime typecheck (tsc --noEmit, and -p tsconfig.scripts.json) exit 0; spec typecheck (tsc --noEmit, check:scripts-typecheck, check:test-typecheck) exit 0. COUNT ATTRIBUTION (spec local total 16872 → 16878, +6): realtime.test.ts 33 → 38 (+5): 'should accept valid event types' replaced by 'names exactly the record-change events the runtime emits (DataEventType + BulkDataEventType)'; new 'refuses the retired value record.created / record.updated / record.deleted / field.changed at the authoring door with its prescription' (4, it.each) and 'keeps zod's own message for a value that was never legal'; scripts/root-entry-type-nameability.pin.test.ts 1 skipped → 2 passed (+1) only because the deletion-leg tree had no spec dist. ABLATIONS (scripts/ablation-replace.mjs WRAP mode inside a trap-restoring script; each restore proven: blob after restore equals the HEAD blob, git diff HEAD empty, git status clean at the end): A, the enum members reverted to the four old values (anchor x1 → x0, blob 0101aeff → d0843a49): realtime.test.ts 15 failed / 23 passed — the emitted-set pin, the 4 refusal pins, 4 SubscriptionEvent and 6 Subscription fixtures red (predicted direction: red). B, the per-value error map deleted (blob 0101aeff → 1a403a38): 4 failed / 34 passed — exactly the 4 refusal pins. No dist leg is owed: realtime.test.ts imports ./realtime.zod and ./events.zod by relative path (src). GATES: dispatch-gates --ran reconciliation at 0b2a767: 117 derived, 112 run, 5 NOT-MEASURED, 0 UNRUN, every line with its exit code. 111 exit 0, including check:adr-0087-registration (registered realtime-event-type-unemitted-values-retired, new here), check:changeset-no-major, check:empty-changeset, check:liveness, check:migration-registry, check:spec-changes, check:upgrade-guide (step-18 entries are not projected yet, so both artifacts are unchanged), check:generated (15/15 current), check:api-surface, check:authorable-surface, check:docs, check:doc-authoring (16788 customer-facing spec strings clean), check:nul-bytes, check:cross-package-test-inputs, check:spec-docblock-symbol-anchors, check:tenant-audit-census. 1 exit 1: check:platform-checklist, red identically in a clean ba5927f tree (control), not CI-wired, unrelated (see findings). NOT MEASURED: check:dual-build-cjs-loads, check:i18n, check:lean-entry-closure, check:type-check-debt, spec check:skill-examples; reason: each refuses with PREREQUISITE NOT MET (exit 3) for dists of packages outside this diff (whole-repo build, CI-owned); no skill example names a RealtimeEventType value. LINT, narrowed and measured: eslint --no-inline-config --format json over the 11 changed paths at 0b2a767; population from eslint.config.mjs files globs = the 6 changed .ts files (the .md / .mdx / .json match no config entry); 6 files reported, 0 errors, 0 warnings; invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no typed rules) and the diff does not touch it, so no untouched file's verdict can move. DOGFOOD authz-conformance: NOT MEASURED by run (needs the @objectstack/verify closure build); its two regexes over in-memory-realtime-adapter.ts replicated at ba5927f and at HEAD: 1 publish GATE_PIN / 0 handleUpgrade tripwire at both.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 — (1) fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executing pr_create = POST /repos/objectstack-ai/objectstack/pulls, PR #20567 opened draft, body read back byte-identical; (2) this report comment via scripts/pm/post-stamped.mjs on issue 20288. One refused attempt (label-write --assign, below) wrote nothing. git push is not counted.",
"deviations": [
"scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 20567 --assign os-justin was refused by the local auto-mode classifier (External System Writes) before any network call. Not retried by another route. The PR assignee (os-justin, the card's) is left for the seat.",
"The PR body opens with 'Closes #20288' (the dispatch's wording) where os-dev.md says 'Fixes #n'; both close the card identically.",
"The PR body is short, as the dispatch asked, and carries no test evidence; the seat's rewrite should cite the tests field above at 0b2a767.",
"The ledger _note line carries, beside the pointers to #14646 ruling A and #8347 ruling 甲, one clause saying RealtimeEventType was repointed and that the events.type row's member list is as measured at 5f5511f, so the ledger does not contradict the enum. It is still a one-line diff.",
"origin/main moved 10 commits past ba5927f during the run; none touches a path in this diff (checked by name list), so no merge was made. CI on the PR merge ref and the queue cover the joint state."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · service-knowledge knowledge-service-plugin.ts:218-231 branches on record.created / record.updated / record.deleted off its IRealtimeService subscription, which no producer ever feeds (the engine publishes data.record.* and data.records., the metadata manager metadata.); the branch also reads payload.record ?? payload, a consumer-side fallback for a legacy shape. Stale TSDoc at :24 ('Subscribe to ObjectQL record.* events') and knowledge-service.ts:232, :252. Dead code, outside this claim's surface; the narrowing here does not change what it reads · noted, not filed · dedupe words: knowledge-event-sync, record.created legacy branch, handleRecordUpsert payload.record",
"carrier: 承接者:无 · packages/spec/liveness/README.md:945 (realtime_subscription row) and packages/spec/scripts/liveness/check-liveness.mts:452-458 describe RealtimeEventType's four old members as current; historical enrolment prose outside the claim's surface. The ledger JSON's one-line _note records the repoint · noted, not filed",
"carrier: the subscription-spelling convergence stage the seat may propose · websocket.zod.ts:104 EventSubscriptionSchema.events describe example names 'record.*' · noted, not filed",
"carrier: 承接者:无 · client/src/realtime-api.ts:153-159 comment says data.field.changed is 'declared in DataEventType, no producer today'; it left DataEventType in 17.0.0 · noted, not filed",
"carrier: platform-checklist-watchdog.yml (files a daily issue while main is red) · pnpm check:platform-checklist exits 1 on ba5927f with areas/identity-auth.json ABSENT SYMBOL packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor; control: the same red in a clean ba5927f tree; not CI-wired by maintainer decision · noted, not filed"
]
}
objectstack-fleet commentedon Sep 29, 2026
Surface extension to claim
5882309567, after the at-tier PASS5883544890on PR #20567 · 2026-09-29T04:17ZFile surfaceadds two texts this change makes false:packages/spec/liveness/README.md, therealtime_subscriptionrow (:945 atorigin/main): its present-tense sentence saysRealtimeEventType's four members are DISJOINT from what the engine publishes.liveness/ships in the@objectstack/spectarball.packages/spec/scripts/liveness/check-liveness.mts:450-459: the same sentence, in a comment.Each gets the qualifier the JSON
_notealready carries (repointed by #20288; the member list as measured at5f5511f0). Nothing else in either file moves.The claim fenced these texts out while the change falsified them. That was the seat's composition error: a fence that forbids touching a text the change makes false. The fix rides this PR as a new head, and the at-tier review runs again on that head before enqueue.
Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
os-dev-report
{
"issue": 20288,
"status": "done",
"branch": "claude/issue-20288-realtime-event-type-emitted-vocabulary",
"pr": "#20567",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 — subagent run, the parent's id",
"premise_still_valid": true,
"head": "17612ad5ea (one commit on top of the reviewed 0b2a767; no merge of origin/main — the branch does not conflict and no gate asked for one)",
"summary": "Follow-up to review 5883544890 ③ under surface extension 5883551279: the two texts that still said RealtimeEventType's four old members are DISJOINT from what the engine publishes now carry the qualifier the JSON _note already carries. One inserted clause each; nothing else in either file moved. No changeset added (the @objectstack/spec minor changeset already covers the package; no gate asked for more). PR body, labels, draft state and auto-merge untouched.",
"diff": "2 files, +3 / -1. (1) packages/spec/liveness/README.md, realtime_subscription row: inserted ', as measured at
5f5511f0before #20288 repointed the enum at the emittedDataEventType+BulkDataEventTypenames,' right after the member list '(record.created/record.updated/record.deleted/field.changed)' and before 'are DISJOINT from what the engine publishes'. (2) packages/spec/scripts/liveness/check-liveness.mts: two comment lines inserted after '...whose four members' (:452): '(as measured at5f5511f0, before #20288 repointed the enum at the emitted' / 'DataEventType+BulkDataEventTypenames)'.","tests": "DELETION LEG first (both sentences deleted via scripts/ablation-replace.mjs inside a trap-restoring script under the verify lock, at 0b2a767): spec local scripts/liveness 10 files, 263 passed; spec repo scripts/liveness/evidence + proof-registry 2 files, 81 passed; pnpm check:liveness exit 0 — no test asserts a fragment of either sentence. Both restores proven (blob == HEAD blob, git diff HEAD empty, git status clean). The first attempt refused itself before running anything (a non-unique '// ' replacement failed the tool's count check) and was re-run with a unique marker; that first run is a no-op, not a reading. FINAL at 17612ad (verify lock, per-part exits local=0 repo=0): spec local scripts/liveness + scripts/zod-graph.test.ts + src/system/metadata-form-zod-reconciliation.test.ts (every spec test naming check-liveness) 12 files, 356 passed; spec repo project 40 files, 716 passed. Gates at 17612ad, each exit 0: pnpm --filter @objectstack/spec check:liveness, pnpm check:pm-dispatch-gates (1976 self-test cases; newly derived because the diff edits a script), pnpm check:pm-governed-merges and node scripts/pm/bare-root-worklist.mjs --self-test (the other two newly derived families), pnpm check:nul-bytes, pnpm check:issue-citations, pnpm check:doc-authoring, pnpm check:cross-package-test-inputs, node scripts/check-spec-docblock-symbol-anchors.mjs. The remaining 117 families were run at 0b2a767 (report 5883321020); this commit changes only the two prose texts above.",
"mcp_calls": "0",
"api_writes": "1 — this comment, via scripts/pm/post-stamped.mjs through the fleet relay. git push is not counted.",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Sep 29, 2026
Landed: PR #20567 →
maine73ee2ddcab8d08e06281d6580a8483761060ee0· 2026-09-29T06:03Zdomain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), landing record for claim5882309567and surface extension5883551279. The at-tier PASS5884292695is on the final head17612ad5ea; the earlier PASS5883544890was on0b2a76726c. Merged through the merge queue at 2026-09-29T06:03Z.Verified on
origin/main:e73ee2ddcahas one parent,e666636fd9, and is an ancestor oforigin/main.'data.records.deleted'inpackages/spec/src/api/realtime.zod.ts0 → 1.Closes #20288closed this card ascompleted. This act removespm:dispatched; thedomain:andarea:labels and the type label stay.Carried out of this card: #20573 (the
service-knowledgerecord.*branch, filed from the review's ③). The five-spelling subscription convergence the re-grade reserved has no card yet; the seat has not filed one.Generated by Claude Code
feat(spec)!: RealtimeEventType names the emitted data.record.* / data…