Skip to content

fix(cli,service-sms)!: 启动期拒绝表外的 OS_SMS_PROVIDER,而不是静默降级成 LogSmsTransport (#5713) - #5771

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5713-serve-env-provider-options
Aug 6, 2026
Merged

fix(cli,service-sms)!: 启动期拒绝表外的 OS_SMS_PROVIDER,而不是静默降级成 LogSmsTransport (#5713)#5771
baozhoutao merged 2 commits into
mainfrom
claude/issue-5713-serve-env-provider-options

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5713

先测定:未知 provider 的失败方向 = 静默降级(分诊指定的第一步)

issue 正文诚实标注「没追下去看插件拿到未知 provider 会怎样」,分诊把它列为第一步,因为它决定严重度。实测(在 origin/main 上跑一次性 harness,已删除):

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({ to, body })       -> { status: 'sent', messageId: 'dev-sms-...' }

resolveInitialTransportmakeSmsTransport,后者对未知 tag 抛
unknown provider 'twilo',但那个 throw 被插件自己 catch 掉、降级成
LogSmsTransport,只留一行 warn。于是手机验证码登录会回「已发送」,用户永远等不到短信。

按 issue 自己给的判据 ——「如果它静默降级,那就是 #5204 的同级缺陷」—— 本条是同级缺陷,不是消息质量问题。

但前提只成立一半:邮件那扇门早就关着

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

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

改法:同一份 provider 注册表,启动期响亮拒绝

分诊允许的两条路里选了「provider 注册表」那条,完全绕开 packages/services/service-settings(边界规则:未改该包任何文件)。

  1. @objectstack/service-sms 导出词汇表(transports/index.ts):新增
    SMS_TRANSPORT_PROVIDERSisSmsTransportProvider(),并把 SmsProviderTag
    从数组派生,而不再是并列的第二份字面量。makeSmsTransportswitch 对它穷举。
    这正是邮件侧 EMAIL_TRANSPORT_PROVIDERS 的对应物 —— 一份词汇表两个消费方,
    「两份字面量描述一个词汇表」就是 plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 里 settings 下拉框和 transports 走散的原因。
  2. serve.ts 抽出 resolveSmsCapabilityArg(cfgSms, env)(镜像
    resolveEmailCapabilityArg),表外 tag 抛错。capability loop 原样接住:app 显式
    requires: ['sms'] 时是硬启动失败,否则 console.error + 不注册 sms 服务 ——
    与邮件臂在同一个循环里的行为逐字一致。

刻意做的两件事

  • 不索要凭据。 与邮件不同,SMS 的凭据本来就不是启动期输入 —— sms settings
    命名空间在 kernel:ready 绑定,那是它们被文档承认的家。裸的
    OS_SMS_PROVIDER=twilio(密钥存在 Settings 里)是一份完整配置,原样放行。
    被拒绝的只有 settings 永远修不好的那一样东西:表外的 provider tag
  • 不动插件的 fallback。 对「已知 provider + 构造期凭据不全」它是对的(settings
    绑定还能换上可用 transport),也仍然是自建宿主的最后一道防线。本 PR 只是让
    os serve 不再喂给它一个它永远用不了的输入。

为什么 #5204 / #5131 那三道闸门看不到这条路

sms 命名空间把 provider 声明为带 options 表的 select;#5131 在写入路径上执行该表;#5204 关掉了 SettingsService 的 env 覆盖分支。三道闸门都在 SettingsService 后面 —— 而这次读取发生在组装 kernel 时,settings 服务还不存在。所以这是同一形状(declared ≠ enforced)在更早生命周期阶段的另一扇门,正如 issue 判断的那样。

反向验证(方向:红,即常规方向)

先声明预期方向再跑:本改动新增一条拒绝,所以把守卫拆掉后,新增的拒绝类断言应当转红,放行类断言应当保持绿。把 resolveSmsCapabilityArg 里的守卫临时短路(还原改动前的裸读)后实测:

x THROWS on a provider tag no transport can deliver ... (#5713)
  -> expected [Function boot] to throw an error
x refuses the same tag declared through config.sms.provider
  -> expected [Function] to throw an error
x reads its vocabulary from @objectstack/service-sms, not a second literal
  -> expected '' to contain 'log / aliyun / twilio'
Tests  3 failed | 5 passed (8)

3 条拒绝钉子全红、5 条放行钉子全绿 —— 与预测一致。脚手架已完整还原(grep REVERSE-VERIFICATION = 0 命中)。

测试

  • 新增 packages/cli/src/commands/serve-sms-capability.test.ts(8 例):默认 log
    显式 log 仍能启动(这是拒绝其余取值的前提)、env 压过 config、大小写归一化、
    三个合法 tag 无凭据放行、providerOptions/retries 透传(含 retries: 0)、
    env 与 config.sms.provider 两个声明渠道各自的拒绝、以及错误消息枚举的就是导出的词汇表。
  • packages/services/service-sms/src/transports/transports.test.ts 增 2 例:词汇表恰好是
    makeSmsTransport 能建的那三个(逐个真建一遍),以及 isSmsTransportProvider
    twilo / 非字符串 / LOG 的判定。
  • 本地:@objectstack/cli 84 files / 833 tests 全绿;@objectstack/service-sms
    3 files / 30 tests 全绿;两包 typecheckbuild、eslint 均通过;
    check:nul-bytes / check:service-providers / check:durability-log-level /
    check:startup-registry-verdict / check:type-check-coverage /
    check:published-files 全绿。

Changeset

@objectstack/cli: major(一种配置的破坏性变更)+ @objectstack/service-sms: minor(新导出),与 #5132 邮件同形改动的 changeset 取同一档位。


Generated by Claude Code

…ead of silently degrading to LogSmsTransport (#5713)

`os serve` read `OS_SMS_PROVIDER` / `config.sms.provider` while assembling the
kernel and handed the string to `SmsServicePlugin` with nothing to compare it
against. The plugin caught the resulting `makeSmsTransport: unknown provider`
throw and substituted `LogSmsTransport`, so a typo booted a server whose
`send()` answers `status: 'sent'` and delivers nothing.

This path never reaches `SettingsService`, which is why the `sms` namespace's
`select` options table, its write-path enforcement (#5131) and the env-override
gate (#5204) could none of them see it.

`resolveSmsCapabilityArg` is extracted (mirroring `resolveEmailCapabilityArg`)
and throws on a tag outside the vocabulary; the capability loop turns that into
a hard boot error for a declared `requires: ['sms']`, else a loud console.error.
Credentials are deliberately NOT demanded here — the settings namespace binds
them at kernel:ready.

`@objectstack/service-sms` exports `SMS_TRANSPORT_PROVIDERS` /
`isSmsTransportProvider`, with `SmsProviderTag` derived from the array, so the
CLI reads the vocabulary rather than restating it (#5094).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 4:47am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/service-sms.

22 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/service-sms)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/service-sms)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v14.mdx (via @objectstack/service-sms)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

该页此前只把 OS_SMS_PROVIDER 描述成 settings 的 env 覆盖,读者无从得知它还会在
`os serve` 组装 kernel 时(settings 服务尚不存在)被读一次 —— 而新的启动期拒绝正
发生在那一次。补上两个读取时刻的区分、被检查的只有 provider tag(凭据仍由该命名空间
在 kernel:ready 提供)、以及 OS_SMS_PROVIDER=log 这个明确的退出方式。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 04:53
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit de770bf Aug 6, 2026
25 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5713-serve-env-provider-options branch August 6, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants