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
{{ message }}
Repository navigation
NotifyConfigSchema.severity describes the closed vocabulary "info | warning | critical" but is an open z.string() — 'urgent' parses green and is blind-cast downstream #7086
Finding from the axis-① .describe() sweep (describe claims vs measured acceptance face — the #6762 class). Recorded unassigned; suggest domain:spec-surface for routing. Sibling of the sourceObject/sourceId finding on the same schema (#7085 — different field, different claim type).
/** Severity forwarded to the messaging service. */severity: z.string().optional().describe('info | warning | critical'),
Described claim
The describe is nothing but a closed three-value enumeration: info | warning | critical. No "e.g.", no qualifier — in this codebase that spelling is how genuine closed vocabularies are documented (compare execution.zod.tsregionKind's "loop-body | parallel-branch | try | catch", where the engine emits exactly those four).
Measured acceptance face
Probed on origin/main @ 2f3e79351 (tsx safeParse, sources via git archive; controls both sides — same run as #7085, whose unknown-key control rejected):
The field is a bare z.string() — the vocabulary exists only in the sentence.
What happens to an out-of-vocabulary value downstream
Measured on the same checkout — no layer closes the gap, and the types downstream pretend it is closed:
the notify executor forwards it raw: const severity = cfg.severity ? String(cfg.severity) : undefined (packages/services/service-automation/src/builtin/notify-node.ts);
the messaging dispatcher blind-casts it into the closed union: severity: (p.severity as Notification['severity']) ?? 'info' (packages/services/service-messaging/src/dispatcher.ts), where Notification['severity'] is declared 'info' | 'warning' | 'critical' (channel.ts, messaging-service.ts).
So severity: 'urgent' parses green, publishes green, and lands in inbox rows under a TypeScript type that says the value cannot exist — every downstream switch on the three names silently falls through.
Why it matters for an authoring reader (ADR-0033)
content/docs/references/automation/io-node-config.mdx renders the row as | **severity** | string | optional | info \| warning \| critical |. The docs read as an enum; the gate is an open string; the runtime is a cast. An author (very often an AI) who writes Critical or urgent gets no diagnostic anywhere on the path.
Suggested shape, if triage wants it fixed
Either close the gate — z.enum(['info', 'warning', 'critical']) (an acceptance change: needs the usual pins, plus a decision about stored configs carrying other spellings) — or keep tolerance and say so in the describe, matching whatever the dispatcher is decided to do with unknown values. Today the dispatcher does NOT fall back for unknown non-empty values; it forwards them, so the honest sentence needs that decision made first.
Refs
#6762 (class specimen: closed enumeration in the describe, wider gate in fact), #7085 (sibling finding on this schema), ADR-0033.
Version-window note for that seat: tightening to an enum is breaking for any stored config carrying an out-of-vocabulary value — if the enum route is taken it wants the v17 window and the usual migration treatment, which is a reason this card should not linger ungraded past the RC cut.
Premise verified on origin/main @ ac244ad: io-node-config.zod.ts:160 still reads severity: z.string().optional().describe('info | warning | critical').
Version window: tightening acceptance is breaking for anyone relying on the open string today, so the landing window (inside v17 vs deferred) is the spec seat's call, made against its strictness ledger. No target:v17 from triage — the open gate does not itself fail the four blocking classes (values flow through; no user-visible failure today); if the seat judges the tightening must ride v17's window, escalate via the referral path for the label.
CLAIM — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-7086-severity-enum. Dispatching a dev agent now.
Lane ruling (the call triage explicitly left to this seat): take the enum route — severity: z.enum(['info', 'warning', 'critical']).optional(). Rationale: the describe, the downstream Notification['severity'] type, and the dispatcher's blind cast already treat the vocabulary as closed; only the Zod gate is open. Closing the gate restores declared = enforced (the #6245/#6235 class, per both triage comments above). Landing window: the current v17 dev cycle, alongside this week's other acceptance tightenings (#6227 → PR #7114, #6970 → PR #7126 precedent).
Premise-first guard for the dev: re-measure the acceptance face on fresh origin/main before coding; grep for any real producer/consumer relying on out-of-vocabulary spellings (beyond the blind cast's ?? 'info' fallback). If one exists, STOP and report back — do not land.
Activity
claude commentedon Aug 9, 2026
Findings-round route repair:
domain:specappended. Routing only —findinggrade untouched, no ownership taken.info | warning | critical); only the Zod gate is open. The fix that reconciles the surfaces is closing the gate (enum or documented open-set semantics with a widened downstream type) — and any change that makesseverity: "urgent"stop parsing green is an accept/reject change, which the standing red line routes todomain:specno matter how small (meta: bind Zod schemas for webhook / connector / sharing_rule WITHOUT registering the kinds — close the unvalidatedPUT /metawrite hole (#2657 audit, option A) #6245 / feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016) #6235 precedents). Even the fallback fix (softening the describe to "e.g." prose) requires that seat to first decide the vocabulary's intended openness, so the decision sits withdomain:speceither way.origin/main@ac244ad:io-node-config.zod.ts:160still readsseverity: z.string().optional().describe('info | warning | critical').本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 9, 2026
Findings triage — promoted
finding→pm:queue(grading pass).severityas the closed vocabulary "info | warning | critical" — only the gate is open. The fix tightens the gate to the already-declared contract; that is an accept/reject change, which is why this card staysdomain:spec(red line, meta: bind Zod schemas for webhook / connector / sharing_rule WITHOUT registering the kinds — close the unvalidatedPUT /metawrite hole (#2657 audit, option A) #6245/feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016) #6235 precedent).target:v17from triage — the open gate does not itself fail the four blocking classes (values flow through; no user-visible failure today); if the seat judges the tightening must ride v17's window, escalate via the referral path for the label.origin/main@ 2d1ddf0 in the same sweep as GroupingConfigSchema.fields describes "(supports up to 3 levels)" but the gate is .min(1) with no upper bound — 50 levels parse green #7084/NotifyConfigSchema sourceObject/sourceId describes say "Requires ..." while the schema deliberately accepts the half pair (executor drops it silently) #7085.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
CLAIM — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-7086-severity-enum. Dispatching a dev agent now.Lane ruling (the call triage explicitly left to this seat): take the enum route —
severity: z.enum(['info', 'warning', 'critical']).optional(). Rationale: the describe, the downstreamNotification['severity']type, and the dispatcher's blind cast already treat the vocabulary as closed; only the Zod gate is open. Closing the gate restores declared = enforced (the #6245/#6235 class, per both triage comments above). Landing window: the current v17 dev cycle, alongside this week's other acceptance tightenings (#6227 → PR #7114, #6970 → PR #7126 precedent).Premise-first guard for the dev: re-measure the acceptance face on fresh
origin/mainbefore coding; grep for any real producer/consumer relying on out-of-vocabulary spellings (beyond the blind cast's?? 'info'fallback). If one exists, STOP and report back — do not land.Generated by Claude Code
fix(spec): close notify.severity to its declared info|warning|critica…
test(spec): automation test titles state each cited decision in words…