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
做 #5094 时路过发现,与该 issue 的修复范围无关(#5094 只收紧了 mail 一个 manifest 的选项表),单独记录。
现状
SettingsService.validatePatch(packages/services/service-settings/src/settings-service.ts,约 618-700 行)的 docstring 写明它「fulfilling the spec promise that required is enforced server-side」,实际执行的校验只有两项:
做 #5094 时路过发现,与该 issue 的修复范围无关(#5094 只收紧了
mail一个 manifest 的选项表),单独记录。现状
SettingsService.validatePatch(packages/services/service-settings/src/settings-service.ts,约 618-700 行)的 docstring 写明它「fulfilling the spec promise thatrequiredis enforced server-side」,实际执行的校验只有两项:required+ visible + 空 → 拒绝;pattern(text)不匹配 → 拒绝。select型 specifier 的options从头到尾没有参与校验。 于是任意字符串都能被写进一个下拉字段:这不是
mail专有的:storage.adapter、sms.provider、ai.provider、localization.date_format等所有type: 'select'的键都一样。影响
manifest 里的
options今天是纯前端约定 —— 控制台下拉框只会发出合法值,所以走 UI 的管理员碰不到;但PUT /api/settings/:ns是公开的可授权面,脚本、迁移工具、AI 写的初始化代码都可以直接写入枚举外的值,而且写入后一路静默:值存下来了,读回来了,消费端各自随机应对。#5094 处理的存量
sendgrid/ses正是这种值的一个实例 —— 那批是历史 manifest 留下的,而这条缺口意味着同样的值今天仍然可以被重新写进去,#5094 在 manifest 侧收紧的契约在 API 侧没有对应的闸门。同类形态在本仓的判例是「declared ≠ enforced」:声明了枚举就要在写入期强制,否则声明只是注释。建议
在
validatePatch里给select(以及multiselect,如果有)加一条:值非空且不在options的value集合内 →FieldError,code用新的invalid_option(ADR-0114 要求 constraint kind 在失败点打戳,不要让路由层从文案反推),constraint带上允许值列表,让客户端能自己组织文案。两个需要一起想清楚的点,建议实现时先给出判断:
from_name的 patch 不会因为库里躺着一个老的provider值而被整体拒绝。否则任何带历史脏值的工作区都会被锁死在设置页里改不动任何东西 —— 那比现在更糟。allowCustom之类的显式声明,而不是靠消费端宽容。这一步会动packages/spec,而 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 / dashboard widgetcompareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011 正在 spec 内作业,车道未空 —— 若结论是需要 spec 改动,应拆单排队。