Observation-class finding, noticed while closing the NotifyConfigSchema.severity gate for #7086 (PR pending). Recorded unassigned. Type-level only — no user-visible failure today, which is why it carries finding and no pm:queue.
Anchor
packages/services/service-messaging/src/outbox.ts, DeliveryPayload:
/** Rendered content snapshot carried on the delivery row for dispatch. */
export interface DeliveryPayload {
title?: string;
body?: string;
severity?: 'info' | 'warning' | 'critical' | string;
actionUrl?: string;
[k: string]: unknown;
}
The defect
'info' | 'warning' | 'critical' | string is exactly string to TypeScript — a
literal union member is absorbed by the wider primitive it is unioned with. The three
names therefore contribute nothing but an editor hint; severity: 'urgent' type-checks
here with no error, and so does severity: ''.
The result is a type that reads as the closed vocabulary while enforcing the open one —
the same declared-vs-enforced shape as #7086, one layer down and in the type system
rather than in Zod. It sits in the package that also owns the genuinely closed
declarations, which is what makes it easy to misread:
messaging-service.ts:64 — readonly severity?: 'info' | 'warning' | 'critical';
channel.ts:40 — readonly severity?: 'info' | 'warning' | 'critical';
inbox-message.object.ts:88 — a Field.select offering exactly those three
so DeliveryPayload is the one member of that family that is silently open.
Why it matters
DeliveryPayload is the rendered snapshot carried on the delivery row. Anything that
builds or mutates one gets no compiler signal when it writes an out-of-vocabulary
severity, and the value then reaches inbox-channel.ts's severity: n.severity ?? 'info'
and the sys_inbox_message.severity select column, whose options are the three names.
Nothing today is measured to write a bad value — this is a missing guard-rail, not a
live bug — but it is the guard-rail that would have made #7086's 'urgent' visible to
tsc instead of only to a probe.
Suggested shape, if triage wants it fixed
Drop the | string so the union means what it says. If some caller genuinely needs to
carry an unvalidated severity through, that is a real decision worth stating explicitly
(e.g. a named string & {} escape or a comment recording the tolerance), not something
the current spelling communicates — as written it looks like a closed set and behaves
like an open one, which is the worst of both.
Worth checking in the same pass whether other 'a' | 'b' | string unions exist in the
services layer; this one was found by eye, not by a sweep.
Refs
#7086 (the Zod-side twin, being closed now), #6762 (the describe-claims-closed class).
Observation-class finding, noticed while closing the
NotifyConfigSchema.severitygate for #7086 (PR pending). Recorded unassigned. Type-level only — no user-visible failure today, which is why it carriesfindingand nopm:queue.Anchor
packages/services/service-messaging/src/outbox.ts,DeliveryPayload:The defect
'info' | 'warning' | 'critical' | stringis exactlystringto TypeScript — aliteral union member is absorbed by the wider primitive it is unioned with. The three
names therefore contribute nothing but an editor hint;
severity: 'urgent'type-checkshere with no error, and so does
severity: ''.The result is a type that reads as the closed vocabulary while enforcing the open one —
the same declared-vs-enforced shape as #7086, one layer down and in the type system
rather than in Zod. It sits in the package that also owns the genuinely closed
declarations, which is what makes it easy to misread:
messaging-service.ts:64—readonly severity?: 'info' | 'warning' | 'critical';channel.ts:40—readonly severity?: 'info' | 'warning' | 'critical';inbox-message.object.ts:88— aField.selectoffering exactly those threeso
DeliveryPayloadis the one member of that family that is silently open.Why it matters
DeliveryPayloadis the rendered snapshot carried on the delivery row. Anything thatbuilds or mutates one gets no compiler signal when it writes an out-of-vocabulary
severity, and the value then reaches
inbox-channel.ts'sseverity: n.severity ?? 'info'and the
sys_inbox_message.severityselect column, whose options are the three names.Nothing today is measured to write a bad value — this is a missing guard-rail, not a
live bug — but it is the guard-rail that would have made #7086's
'urgent'visible totscinstead of only to a probe.Suggested shape, if triage wants it fixed
Drop the
| stringso the union means what it says. If some caller genuinely needs tocarry an unvalidated severity through, that is a real decision worth stating explicitly
(e.g. a named
string & {}escape or a comment recording the tolerance), not somethingthe current spelling communicates — as written it looks like a closed set and behaves
like an open one, which is the worst of both.
Worth checking in the same pass whether other
'a' | 'b' | stringunions exist in theservices layer; this one was found by eye, not by a sweep.
Refs
#7086 (the Zod-side twin, being closed now), #6762 (the describe-claims-closed class).