feat(spec): EmailProviderSchema 补上 'smtp',TSDoc 与 plugin-email 实际能力对齐 (#5104) - #5308
Merged
Conversation
…#5104) `EmailProviderSchema` 停在 log/resend/postmark,而它自己的 TSDoc 还在说 「Self-hosted SMTP is intentionally NOT shipped in plugin-email」。#5087 之后 两句都不成立:plugin-email 已内置 SmtpTransport(ADR-0012,nodemailer 惰性 import),CLI 接受 OS_EMAIL_PROVIDER=smtp 与 OS_EMAIL_SMTP_* 一族。 于是形成 spec 落后于运行时的 declared != implemented:用 EmailServiceConfig 标注 objectstack.config.ts 的作者写 provider: 'smtp' 会拿到类型错误,而该 provider 早就能正常投递。 - EmailProviderSchema 增加 'smtp'(纯加值,无需 ADR-0087 conversion) - 重写 TSDoc:SMTP 内置、nodemailer 惰性加载;写明 sendgrid/ses 不是成员 (从未实现 HTTP-API transport,走 provider='smtp',见 #5094) - provider / options 补 .describe(),参考文档正面说明 smtp 连接参数与 OS_EMAIL_SMTP_* 的一一对应 - 跨包契约测试把 spec 词表与 plugin-email 的 EMAIL_TRANSPORT_PROVIDERS 双向 锁死(运行时集合相等 + 编译期互相可赋值) - 重新生成 content/docs/references/system/email-config.mdx 运行时零改动:plugin-email 与 CLI 未被修改,新增的仅为测试文件。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
…il-provider-types
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 4, 2026
…il-provider-types
os-zhuang
marked this pull request as ready for review
August 4, 2026 21:14
os-zhuang
enabled auto-merge
August 4, 2026 21:14
os-zhuang
pushed a commit
that referenced
this pull request
Aug 4, 2026
Second relay merge: #5300 / #5304 / #5306 / #5308 / #5318 / #5326 / #5327. Textually clean, but the os-regen driver defers generated artifacts rather than text-merging them, so `json-schema.manifest.json` again came out holding this branch's pre-merge side — this time still listing `ui/EmbedConfig` and `ui/NotificationAction`, both retired by #5300. Reset the deferred artifacts to `origin/main`, rebuilt from the merged tree, regenerated wholesale. Post-regen assertions (a silent one-side drop is exactly what this catches): api-surface delta vs `origin/main` is exactly this PR's four additions and ZERO removals; manifest delta is one addition (`ui/ViewItemWire`) and zero removals; every sibling retirement stays removed (`ui/EmbedConfig`, `ui/NotificationAction`, `system/HttpServerConfig`, `ui/Animation`, `ui/ZIndex`) and every sibling addition stays present (`FilterArray` ×7, `EmailProvider` ×2). `check:authorable-surface` (+ its #5304 `.base.json` anchor) is green and the anchor file is byte-identical to `origin/main` — not hand-edited. `metadata-form-zod-reconciliation.test.ts` co-edited with #5280/#5318 and merged SEMANTICALLY, not by taking a side: #5318 rewrote the docblock, imports, helpers and test bodies, while this PR's only edit is `unwrap`'s `pipe` case, so the two did not overlap textually — but they do interact, and in the direction that matters. #5318's `isRetiredAt` / `authorableKeysOf` both route through `unwrap`/`keysOf`, and `view`'s root is now a `z.preprocess` pipe. Measured both ways: without this PR's #4488-style fix `unwrap(view root)` resolves to `transform` and `keysOf` returns NULL, so #5318's brand-new tombstone assertions would be VACUOUS on `view` (and the pre-existing key-bearing assertion would fail outright); with it, 89 keys. Both PRs' assertions are live on every type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5104
先厘清派发中的矛盾:实测结论是
smtp,不是ses本单正文(04:39Z)说要加
'smtp';12:31Z 的释放认领评论却给了另一套派发包 ——「EmailConfigSchema的 transport 判别联合加ses分支(region必填、凭证可选走默认链)」。两者不能都当真,所以第一步是在origin/main(@ 5993c5b)上实测@objectstack/plugin-email到底发布了什么。实测结果(
packages/plugins/plugin-email/src/transports/index.ts):SmtpTransportsrc/transports/smtp.ts,nodemailer惰性 import(ADR-0012)SesTransportOS_EMAIL_SMTP_HOST/_PORT/_SECURE/_USER/_PASSWORDpackages/cli/src/commands/serve.ts)OS_EMAIL_SES_*OS_EMAIL_PROVIDER=sesresolveEmailCapabilityArg用 plugin-email 的isEmailTransportProvider把关,serve-email-capability.test.ts明确断言它 throwEmailConfigSchema/ transport 判别联合email-config.zod.ts只有EmailServiceConfigSchema(平铺z.object),没有任何判别联合更关键的是,
ses在 plugin-email 里是被显式退役的 tag,还带着迁移话术:结论: 12:31Z 评论说的「plugin-email 的 SES 支持已在」与 main 相反,它描述的
EmailConfigSchema判别联合也不存在。按本单的硬规则(spec 不得承诺 main 上没有的 provider),给 spec 加ses会制造反方向的 declared != implemented —— 正好是 #5094 被立单的那个缺陷。因此本 PR 按正文原始范围执行:只加'smtp',并把ses/sendgrid的缺席写进 TSDoc 说明理由。本单正文的前提经复核成立。改动
运行时零改动。 plugin-email 与 CLI 未被修改;本 PR 在 plugin-email 下只新增了一个测试文件。
EmailProviderSchema增加'smtp'—— 纯加值,已有配置不受影响,无需 ADR-0087 conversion / migration。IEmailTransportthemselves」—— plugin-email: 实现 SMTP transport —— 设置页可选 SMTP 但后端无实现 #5087 之后整段为假。新文写明 SMTP 由 plugin-email 内置、nodemailer惰性加载,并解释sendgrid/ses为何不是成员(从未实现 HTTP-API transport,都走provider: 'smtp')。provider/options补.describe()。原先这两格在生成文档里是空的;现在参考文档正面说明provider: 'smtp'的连接参数落在options的host(必填)/port/secure/user/password,与OS_EMAIL_SMTP_*一一对应。content/docs/references/system/email-config.mdx。关于「TSDoc 仍在劝人自带 transport」
正文担心生成文档里还留着这句话 —— 实测它从未出现在文档里:
EmailProviderSchema的 schema 级 TSDoc 不参与build-docs.ts渲染,只有文件头块注释和.describe()会。所以那句假话的受害面是读源码/IDE 悬浮的作者(含 AI),文档侧的缺陷只是 Allowed Values 少一个值。两处都已修:枚举补值修文档,TSDoc 重写修源码,.describe()则把 SMTP 的配置方式正面写进文档。测试:双向锁死 provider 词表
按验收要求「把枚举的接受集钉到实测的运行时集合,让下一个 provider 必须两边都有意识地改」,分两侧:
packages/spec/src/system/email-config.test.ts(新增) —— 全部是运行时safeParse断言。packages/spec 的tsconfig.json排除了**/*.test.ts,tsc --noEmit根本不读这个文件(@ts-expect-error退役 pin 在packages/spec里是幽灵检查:tsconfig 把**/*.test.ts排除出唯一的tsc --noEmit#5286),所以在这里写Assert< Equal< … > >一类类型级断言会是幻检查。已在文件顶部注明这一点。packages/plugins/plugin-email/src/transports/spec-provider-parity.contract.test.ts(新增,测试文件) —— 跨包契约,把EmailProviderSchema.options与EMAIL_TRANSPORT_PROVIDERS双向比对,并要求 spec 声明的每个值都能被makeTransport真正造出来。这是同一词表的第三条边:
mail-manifest-providers.contract.test.ts已经把设置页下拉框钉到 transports,本 PR 把授权契约也钉上去。放在 plugin-email 而不是 spec,理由与那个既有测试写的一样 —— 两侧各钉一个字面量,永远可以靠改另一个字面量「修好」,而这正是 #5094 里两者漂移的方式。@objectstack/spec本就是 plugin-email 的正式依赖,无新增运行时边。注意两侧的不对称:plugin-email 的 tsconfig 不排除测试文件,所以那边的编译期见证是真的 —— 下面已实证。
反向验证:预判方向 = 红,三处全部命中
动手前先预判:把枚举退回三个值,新测试应当变红(标准方向,非 #5046 的计数反转、也非 #5009 的倒置)。实测把
email-config.zod.ts退回z.enum(['log', 'resend', 'postmark'])并重新构建 spec 后:A. plugin-email 的
typecheck(编译期见证,证明它不是幻检查):B. plugin-email 的契约测试(运行时集合相等):
C. spec 侧测试:
随后已还原并重新构建。
验证(合入 origin/main 之后重跑)
生成物纪律:
--fix只重算了被证明过期的那一项(content/docs/references/**);check:api-surface(无导出增删)、check:authorable-surface、check:strictness-ledger本来就是绿的 —— 本 PR 没有新增可授权键(只是给既有provider键的枚举加了一个值),所以 #5220 的分类账纪律不触发,gen:strictness-ledger未运行、计数未变。同步:已
git merge origin/main(⛔ 无 rebase / force-push)带入 #5294。它只碰 plugin-auth / verify / qa,与本 PR 零重叠,os-regen-pending为空;合并后 9 项闸门 + 两包 test/typecheck 已如上重跑。相对 origin/main 的 delta 恰为 5 个文件。node scripts/check-nul-bytes.mjs通过;另对本 PR 全部文件做了闸门覆盖不到的自扫(grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'),无控制字节。范围外发现
#5307 ——
EmailServiceConfigSchema未声明 CLI 实读的queueDelivery/appName/defaultTemplateContext,与本单同族、不同键(queueDelivery是 #5160 落地的耐久队列开关,作者标注类型后写它会拿到类型错误)。不并进本 PR:那需要新增可授权键,要走 #5220 的 strictness-ledger 纪律,体量与风险都不同,合并会让本 PR 的 diff 失焦。已建议与本单串行(同文件 + 同 os-regen 生成物)。changeset:
@objectstack/specminor(追平既成事实的加值型契约变更)。⛔ 未改
content/docs/releases/;未新增文档页面。🤖 Generated with Claude Code
https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
Generated by Claude Code