Observed while running the per-surface identifier census for #12245. Filed unassigned as a finding, not a defect claim.
What was observed
EventNameSchema (packages/spec/src/shared/identifiers.zod.ts) is bound by exactly
three schemas, and by nothing else in either repository:
| Binding | Site | Runtime consumer? |
|---|
EventTypeDefinitionSchema.name | kernel/events/core.zod.ts:94 | none |
EventSchema.name | kernel/events/core.zod.ts:122 | none |
EventMessageSchema.eventName | api/websocket.zod.ts:299 | none |
Reproduced on origin/main at e2debee6: outside kernel/events/core.zod.ts and
api/websocket.zod.ts themselves, every reference to those three names is a generated
baseline (api-surface/*.json), the file's own test, type-alias-convention.pin.test.ts,
CHANGELOG.md, or V3_MIGRATION_GUIDE.md. No plugin, service, driver, or app parses
through any of them.
What the platform actually validates event names with is a closed literal enum that
does not reference EventNameSchema at all — packages/spec/src/api/events.zod.ts:
exportconstDataEventType=z.enum(['data.record.created','data.record.updated','data.record.deleted',]);exportconstBulkDataEventType=z.enum(['data.records.updated','data.records.deleted',]);
grep -c EventNameSchema packages/spec/src/api/events.zod.ts returns 0.
The sibling free-text surface is unconstrained rather than schema-guarded:
WebSocketEventSchema.channel is a bare z.string() (api/websocket.zod.ts:468) whose
own describe() documents "record.account.123" and "user.456" as legitimate channel
spellings.
Why it may matter
EventNameSchema is presented as the platform's event-name grammar — the docblock on
SystemIdentifierSchema lists "Event keys" among the surfaces it governs, and
EventNameSchema carries its own worked examples. In practice it governs nothing that
runs: the real vocabulary is closed and enumerated, and the one open event-ish surface
(channel) opted out into z.string().
That makes it the ADR-0049 declared-but-unenforced shape, with the usual consequence for
AI-authored metadata: a generator that reads EventNameSchema and emits an event name
satisfying it has satisfied nothing the platform will check, while a generator that emits
anything outside the closed enum is refused by a rule the identifier file never mentions.
Two routes, neither obvious and neither recommended here:
- Bind it — have the open event surfaces (starting with
channel, and any future
custom-event registration) run through EventNameSchema, making the declared grammar
the enforced one. - Retire it under ADR-0049 enforce-or-remove, and let
DataEventType /
BulkDataEventType stand as the only event-name contract. This is a published-export
removal, so it takes the ADR-0087 conversion.
Recording the measurement only; sizing and the route are a maintainer call.
Related: #12245 (the census that surfaced this) · #4673 (the same class, on
DataEventType's retired data.field.changed member) · ADR-0049.
Observed while running the per-surface identifier census for #12245. Filed unassigned as a
finding, not a defect claim.What was observed
EventNameSchema(packages/spec/src/shared/identifiers.zod.ts) is bound by exactlythree schemas, and by nothing else in either repository:
EventTypeDefinitionSchema.namekernel/events/core.zod.ts:94EventSchema.namekernel/events/core.zod.ts:122EventMessageSchema.eventNameapi/websocket.zod.ts:299Reproduced on
origin/mainate2debee6: outsidekernel/events/core.zod.tsandapi/websocket.zod.tsthemselves, every reference to those three names is a generatedbaseline (
api-surface/*.json), the file's own test,type-alias-convention.pin.test.ts,CHANGELOG.md, orV3_MIGRATION_GUIDE.md. No plugin, service, driver, or app parsesthrough any of them.
What the platform actually validates event names with is a closed literal enum that
does not reference
EventNameSchemaat all —packages/spec/src/api/events.zod.ts:grep -c EventNameSchema packages/spec/src/api/events.zod.tsreturns0.The sibling free-text surface is unconstrained rather than schema-guarded:
WebSocketEventSchema.channelis a barez.string()(api/websocket.zod.ts:468) whoseown describe() documents
"record.account.123"and"user.456"as legitimate channelspellings.
Why it may matter
EventNameSchemais presented as the platform's event-name grammar — the docblock onSystemIdentifierSchemalists "Event keys" among the surfaces it governs, andEventNameSchemacarries its own worked examples. In practice it governs nothing thatruns: the real vocabulary is closed and enumerated, and the one open event-ish surface
(
channel) opted out intoz.string().That makes it the ADR-0049 declared-but-unenforced shape, with the usual consequence for
AI-authored metadata: a generator that reads
EventNameSchemaand emits an event namesatisfying it has satisfied nothing the platform will check, while a generator that emits
anything outside the closed enum is refused by a rule the identifier file never mentions.
Two routes, neither obvious and neither recommended here:
channel, and any futurecustom-event registration) run through
EventNameSchema, making the declared grammarthe enforced one.
DataEventType/BulkDataEventTypestand as the only event-name contract. This is a published-exportremoval, so it takes the ADR-0087 conversion.
Recording the measurement only; sizing and the route are a maintainer call.
Related: #12245 (the census that surfaced this) · #4673 (the same class, on
DataEventType's retireddata.field.changedmember) · ADR-0049.