You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(spec): close notify.severity to its declared info|warning|critical vocabulary (#7086) (#7192)
NotifyConfigSchema.severity was a bare z.string() whose .describe() read
'info | warning | critical', so the enumeration existed only in the sentence:
'urgent', 'INFO' and '' all parsed green, were forwarded raw by the notify
executor, and were blind-cast by the messaging dispatcher into a union that
declares those values impossible.
Every other surface already declared the set closed (the describe, the
Notification['severity'] type, the sys_inbox_message.severity select field),
so this closes the last open one.
Safe because the executor reads severity RAW -- it is one of three keys
(channels, topic, severity) that never pass through interpolate() -- so a
{token} template there never resolved. The module JSDoc claimed "every
string-ish value except channels" is interpolated; that was stale for topic
and severity and is corrected here, since the tightening's safety rests on it.
Blast radius is an execute-time refusal, not a load failure: FlowNodeSchema
.config is an untyped record, so stored flows still load and rehydrate.
Also closes the Studio form descriptor to the same set, and extends the
IO-node form/Zod ledger test to reconcile closed value vocabularies rather
than key sets alone.
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
Co-authored-by: Claude <noreply@anthropic.com>
fix(spec): `notify.severity` closes its declared `info | warning | critical` vocabulary at the gate, not only in its describe (#7086)
7
+
8
+
<!-- adr-0087: not-required (no-migration-prescription) A stored flow is unaffected at LOAD: `FlowNodeSchema.config` is `z.record(z.string(), z.unknown()).optional()`, so `NotifyConfigSchema` runs only at EXECUTE time via `parseNodeConfig` — nothing fails to load or rehydrate, which is the population a D2 conversion exists to protect. And no automatic rewrite is correct here: mapping a stored `'urgent'` to `'info'` would silently pick a severity on the author's behalf, which is precisely the blind-cast defect this change removes. The refusal names the three legal values, so the author reconciles it once and keeps their intent. Re-measured across the monorepo: zero out-of-vocabulary spellings in any flow, example, fixture or seed. -->
9
+
10
+
`NotifyConfigSchema.severity` was a bare `z.string()` whose `.describe()` read
11
+
`'info | warning | critical'` — no "e.g.", no qualifier. In this codebase that
12
+
spelling is how a genuine closed vocabulary is documented, so the enumeration
13
+
existed only in the sentence. Measured on `origin/main` before the change:
|**severity**|`string`| optional | info \| warning \| critical|
103
+
|**severity**|`Enum<'info' \| 'warning' \| 'critical'>`| optional | Severity forwarded to the messaging service|
104
104
|**sourceObject**|`string`| optional | Object name of the record the notification links to (writes sys_notification.source_object). Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link. |
105
105
|**sourceId**|`string`| optional | Record id the notification links to (writes sys_notification.source_id). Only takes effect together with sourceObject — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link. |
106
106
|**actorId**|`string`| optional | User id that caused the event (writes sys_notification.actor_id) |
.describe('Severity forwarded to the messaging service'),
161
183
/** Click-through target object — only effective together with `sourceId` (#2675). */
162
184
sourceObject: z.string().optional()
163
185
.describe('Object name of the record the notification links to (writes sys_notification.source_object). Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link.'),
0 commit comments