Skip to content

CLI 启动直读 process.env.OS_SMS_PROVIDER / OS_EMAIL_PROVIDER,完全绕过 settings 的 options 表 —— #5204 的闸门看不到这条路 #5713

Description

@os-zhuang

在 #5204(env 覆盖过 options 表)的实现中发现,记录下来交 triage。不在 #5204 的完成范围内——#5204 修的是 SettingsService 的 env 分支,而这条路根本不经过 SettingsService,所以那道闸门对它无效。

事实

packages/cli/src/commands/serve.ts 在装配 capability plugin 时直接读 process.env,把 provider 名字原样塞进插件构造参数:

  • :2346

    const provider = (process.env.OS_SMS_PROVIDER || cfgSms.provider || 'log').toLowerCase();
    
  • :3121(resolveEmailCapabilityArg)

    const provider = String(env.OS_EMAIL_PROVIDER || cfgEmail.provider || 'log').toLowerCase();
    

两处都没有拿 provider 去比对任何 options 表。sms settings 命名空间的 provider specifier 是带 options 表的 select(packages/services/service-settings/src/manifests/sms.manifest.ts:24),mail 同理(mail.manifest.ts:62),但这段代码取的是构造期的 env,发生在 settings 服务存在之前——代码注释自己写明了这一点:「constructor opts cover pre-settings boot and hosts without the settings service」。

所以 OS_SMS_PROVIDER=twilo(拼错的 twilio)在启动时不会被任何枚举拦住。#5204 关掉的是 SettingsService.get() 那扇门;这是另一扇。

为什么算一类问题

这与 #5204 是同一个形状(declared ≠ enforced:一个声明了合法值集的配置项,存在一条从 env 到消费方、不经过该值集的路径),只是发生在更早的生命周期阶段,因此不能用同一个修法。可能的方向:启动期用同一份 option 表(或 provider 注册表)校验并响亮拒绝/忽略;或者让插件在 kernel:ready 自己断言 provider 已注册。

未核实的部分(诚实标注)

我没有追下去看 SmsServicePlugin / 邮件插件拿到一个未知 provider 之后会怎样——是响亮失败、还是静默落回 log、还是拼进某个 URL。这决定了本条的严重度,但追查它属于 #5204 之外的范围扩张,所以停在这里。如果插件本身已经响亮拒绝未知 provider,那本条降为「消息质量」问题;如果它静默降级,那就是 #5204 的同级缺陷。

相关

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    分诊:入队 pm:queue + domain:cli。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 6, 2026
  3. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    认领:PM 循环第 1 轮(cli 车道,继任会话)
    会话:session_01DWUR56YsttL5sTF72Q75TQ
    分支:claude/issue-5713-serve-env-provider-options
    Worktree:objectstack-issue-5713
    域:domain:cli
    文件面:packages/cli/src/commands/serve.ts(:2346 sms 臂、:3121 resolveEmailCapabilityArg)+ 对应测试 + changeset(越界即停,报告说明)

    执行注记:①按分诊指示,第一步先测定未知 provider 在 SmsServicePlugin / 邮件插件侧的失败方向(响亮失败 or 静默降级),它决定严重度与修法选型;②若修法需要改动 packages/services/service-settings(如新导出共享 options 表),那是跨域文件面 —— 停手报告,⛔ 不直改(仅 import 既有导出不算);③churn:serve.ts 近日被 #5618(turso Phase A)动过,以最新 origin/main 为准。


    Generated by Claude Code

  4. removed their assignment
    on Aug 6, 2026
  5. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    认领解除(cli 车道 PM,session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 04:1xZ):03:19Z 派发的 dev 被维护者手动中止(宿主明确回报 stopped by the user、不会恢复)。零推送、无远端分支,无需回滚。摘 assignee 与 pm:dispatched,回 pm:queue。是否重派等维护者示意。认领评论中的执行注记(第一步测未知 provider 失败方向、services 包边界、churn)对下次派发仍然有效。


    Generated by Claude Code

  6. self-assigned this
    on Aug 6, 2026
  7. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    恢复认领 + 重派(cli 车道 PM,session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 04:2xZ):维护者已示意「重派,继续循环」,04:1xZ 中止门解除。分支 claude/issue-5713-serve-env-provider-options、worktree、执行注记(第一步测未知 provider 失败方向、services 包边界)全部沿用 03:19Z 认领评论;基线取最新 origin/main(现 889ae47)。


    Generated by Claude Code

  8. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    第一步测定结果 + 前提修正(cli 车道 dev,session_01DWUR56YsttL5sTF72Q75TQ,PR #5771)

    1. 未知 provider 的失败方向 = 静默降级(正文「未核实」段的答案)

    一次性 harness 实测(origin/main,已删除):

    new SmsServicePlugin({ provider: 'twilo' }).init(ctx)
      booted_without_throw: true       transport_class: 'LogSmsTransport'
      isConfigured():       false      logger.warn x 1, logger.error x 0
      service.send(...)  -> { status: 'sent', messageId: 'dev-sms-...' }
    

    makeSmsTransport 确实对未知 tag 抛 unknown provider 'twilo',但 resolveInitialTransport 自己把它 catch 掉、降级成 LogSmsTransport,只留一行 warn。按正文给的判据 —— 「如果它静默降级,那就是 #5204 的同级缺陷」—— 本条按同级缺陷处理,不是消息质量问题。

    2. 前提只成立一半:邮件那扇门早就是关着的

    正文说「两处都没有拿 provider 去比对任何 options 表」。字面属实(两处都不读 settings 的 options 表),但邮件臂并不缺校验:resolveEmailCapabilityArg 从 @objectstack/plugin-email 读 EMAIL_TRANSPORT_PROVIDERS,对表外 tag 直接抛错(serve.ts:3209,#5132 关的),serve-email-capability.test.ts 已钉住 sendgrid / ses / mailgun 都 throw。所以两扇门里只有 sms 那扇是开的。

    PR #5771 因此只动 sms 臂,并且把它修成邮件臂现成的形状,而不是给邮件臂再加一层。

    3. 修法与边界

    取分诊允许的「provider 注册表」那条路:@objectstack/service-sms 导出 SMS_TRANSPORT_PROVIDERS / isSmsTransportProvider(SmsProviderTag 改为从数组派生),serve.ts 抽出 resolveSmsCapabilityArg 并对表外 tag 抛错。packages/services/service-settings 一个文件都没动 —— 边界规则遵守,无需上升。

    刻意不索要凭据:SMS 凭据本就不是启动期输入(settings 命名空间在 kernel:ready 绑定),被拒绝的只有 settings 永远修不好的那一样东西 —— 表外的 provider tag。插件自身的 fallback 也未改动,它对「已知 provider + 凭据不全」仍是对的。

    4. 顺手记的 finding

    #5773 —— sms settings 的 provider options 表与 SMS transports 之间没有契约测试(mail 有 mail-manifest-providers.contract.test.ts,sms 没有对应物)。今天两端恰好一致,没有用户会撞上,故按 observation-class 打 finding、未入队、未认领。


    Generated by Claude Code

  9. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    文件面补充申报 + 验收:ACCEPT(cli 车道 PM,session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 05:1xZ)→ PR #5771

    补充申报(认领修正):实施走了分诊预见的「provider 注册表」路线,文件面在原申报(packages/cli/serve.ts + 测试)之外扩至 packages/services/service-sms 三个文件(transports/index.ts 提词汇表 SMS_TRANSPORT_PROVIDERS + index.ts 再导出 + transports 测试)与 content/docs/.../sms-service.mdx 一页。分诊评论已预授权此形态并要求申报,本认领当时漏报,现补:定向在飞检查已跑 —— domain:services 车道当前在飞 0 单,无文件面相交;service-settings 一个文件未动(边界规则遵守)。

    验收要点:

    转 ready + 挂 auto-merge 入队;MERGED 后接续派 #5726(cli 包串行队列)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions