Skip to content

finding(spec): EventNameSchema's only three binding schemas have no runtime consumer — the live event vocabulary is a closed enum that never touches it #13613

Description

@os-warren

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:

export const DataEventType = z.enum([
  'data.record.created',
  'data.record.updated',
  'data.record.deleted',
]);

export const BulkDataEventType = 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:

  1. 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.
  2. 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.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions