来自 #5152 的实现(PR 见下)。不是 #5152 的搭车 ,那边只在自己这一个键的消费端补了拒绝逻辑;这里说的是所有 select/radio/multiselect 键共有的通路。
对着 origin/main(e96ad55c0 之后合入 #5152 分支时现查)。
事实
SettingsService 有两条产出「有效值」的路径,只有一条过 options 表:
写入路径 setMany → validatePatch(settings-service.ts:731 一带)。plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 /fix(service-settings,plugin-email): 邮件服务商下拉框只列真能投递的值 (#5094) #5133 把 sendgrid/ses 从 mail.provider 的 options 里摘掉之后,补上了这道 invalid_option 校验,理由原话是「PUT /api/settings/:ns 是可授权的公开面,脚本、迁移或 AI 生成的 bootstrap 代码可以往 select 里写任意字符串,存下来、读回来,再让每个消费方各自即兴发挥」。有测试钉住(settings-service.test.ts:431 起)。
env 路径 get()(settings-service.ts:309-329):
const envName = envKeyOf ( namespace , key ) ;
const envRaw = this . env [ envName ] ;
if ( typeof envRaw === 'string' ) {
const def = reg . defaults . get ( key ) ;
const value = coerceEnvValue ( envRaw , def ) ;
return { value, source : 'env' , locked : true , ... } ;
}
coerceEnvValue 只按默认值的类型做形状转换,不看 spec.options 。env 值在 cascade 里优先级最高且 locked: true,所以它就是有效值。
结果:OS_MAIL_PROVIDER=sendgrid 会被原样交给 mail 插件——正是 #5094 刚摘掉的那个值,从另一扇门走回来了。同理 OS_BRANDING_THEME_MODE=drak、OS_STORAGE_PROVIDER=s4、OS_AI_PROVIDER=openal 等等。
为什么算缺陷而不是「运维自己负责」
可能的形状(供定夺)
get() 的 env 分支也过 options 表 :不匹配就按 error 记一行(写清后果与合法值集),并忽略该 env 值 回落到 cascade 的下一层。与 membershipPolicy 无法作为平台设置配置,且注册路径与回填路径读的是两个来源 #5152 在消费端采取的姿态一致,但放在唯一正确的地方,一次覆盖所有命名空间。
启动时一次性校验 :registerManifest 时扫描该命名空间所有键的 env 覆盖,非法的直接拒绝启动(对照 OS_TENANCY_POSTURE 「无法识别的值拒绝启动」的先例)。姿态最硬,也最容易把既有部署拦在门外。
什么都不做,只在文档里写——但这就是 plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 明确反对的那种静默。
倾向 1 + 2 的组合 :注册时报一次(够响、够早),运行时忽略非法值(够安全)。但「拒绝启动 vs 忽略」是姿态决定,不该由实现者自己定。
已知的一处已缓解
auth.membership_policy(#5152 )在 bindAuthSettings() 里自己挡了一道:非法值记 error 并保持当前策略,不静默落回 auto。那是单点补丁,不是这个通路的修复——本 issue 修好后,那段消费端逻辑可以留着当第二道防线,也可以收敛。
Found-during: #5152
来自 #5152 的实现(PR 见下)。不是 #5152 的搭车,那边只在自己这一个键的消费端补了拒绝逻辑;这里说的是所有 select/radio/multiselect 键共有的通路。
对着
origin/main(e96ad55c0之后合入 #5152 分支时现查)。事实
SettingsService有两条产出「有效值」的路径,只有一条过 options 表:写入路径
setMany→validatePatch(settings-service.ts:731一带)。plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094/fix(service-settings,plugin-email): 邮件服务商下拉框只列真能投递的值 (#5094) #5133 把sendgrid/ses从mail.provider的 options 里摘掉之后,补上了这道invalid_option校验,理由原话是「PUT /api/settings/:ns是可授权的公开面,脚本、迁移或 AI 生成的 bootstrap 代码可以往 select 里写任意字符串,存下来、读回来,再让每个消费方各自即兴发挥」。有测试钉住(settings-service.test.ts:431起)。env 路径
get()(settings-service.ts:309-329):coerceEnvValue只按默认值的类型做形状转换,不看spec.options。env 值在 cascade 里优先级最高且locked: true,所以它就是有效值。结果:
OS_MAIL_PROVIDER=sendgrid会被原样交给 mail 插件——正是 #5094 刚摘掉的那个值,从另一扇门走回来了。同理OS_BRANDING_THEME_MODE=drak、OS_STORAGE_PROVIDER=s4、OS_AI_PROVIDER=openal等等。为什么算缺陷而不是「运维自己负责」
get()不 warn,/api/settings/:ns的读取面会把这个非法值当成source: 'env'的正常值展示,消费方各自即兴处理(有的 switch 落 default,有的把它当字符串拼进 URL)。可能的形状(供定夺)
get()的 env 分支也过 options 表:不匹配就按error记一行(写清后果与合法值集),并忽略该 env 值回落到 cascade 的下一层。与 membershipPolicy 无法作为平台设置配置,且注册路径与回填路径读的是两个来源 #5152 在消费端采取的姿态一致,但放在唯一正确的地方,一次覆盖所有命名空间。registerManifest时扫描该命名空间所有键的 env 覆盖,非法的直接拒绝启动(对照OS_TENANCY_POSTURE「无法识别的值拒绝启动」的先例)。姿态最硬,也最容易把既有部署拦在门外。倾向 1 + 2 的组合:注册时报一次(够响、够早),运行时忽略非法值(够安全)。但「拒绝启动 vs 忽略」是姿态决定,不该由实现者自己定。
已知的一处已缓解
auth.membership_policy(#5152)在bindAuthSettings()里自己挡了一道:非法值记error并保持当前策略,不静默落回auto。那是单点补丁,不是这个通路的修复——本 issue 修好后,那段消费端逻辑可以留着当第二道防线,也可以收敛。Found-during: #5152