Repository navigation
Description
实施 #5307 时实测 resolveEmailCapabilityArg 的模板上下文解析顺序,发现一处与文档声明相反的优先级。#5307 的 PR 只把实测到的现状写进 TSDoc 并用测试钉住,没有改行为——改不改是契约决定,故单独立单。
实测(origin/main @ ed0d2aa)
packages/cli/src/commands/serve.ts,resolveEmailCapabilityArg 内:
const defaultTemplateContext = {
appName: env.OS_APP_NAME || cfgEmail.appName || configAppName || 'ObjectStack',
...(cfgEmail.defaultTemplateContext || {}),
};appName 先算,defaultTemplateContext 再整体展开覆盖在它上面。于是:
| 来源 | 胜负 |
|---|---|
config.email.defaultTemplateContext.appName |
赢 |
OS_APP_NAME(环境变量) |
输 |
config.email.appName |
输 |
顶层 config.appName |
输 |
复现(无需起服务):
resolveEmailCapabilityArg(
{ appName: 'From The Key', defaultTemplateContext: { appName: 'From The Context' } },
{ OS_APP_NAME: 'From The Env' },
).options.defaultTemplateContext.appName
// => 'From The Context'为什么是问题
EmailServiceConfigSchema 的头部 TSDoc(也是生成文档 content/docs/references/system/email-config.mdx 的开头)写的是:
Resolution order in serve.ts:
1. config.email.* from objectstack.config.ts
2. OS_EMAIL_* environment variables (override per setting)
3. Default -> provider='log'
「环境变量逐项覆盖配置」是这个文件对所有键的承诺,apiKey / defaultFrom / retries / queueDelivery / SMTP 一族都照此执行。只有 appName 这一项,一旦作者同时写了 defaultTemplateContext.appName,env 就失效——而且是静默失效。
具体后果:同一份 objectstack.config.ts 部署到多环境,运维在生产设 OS_APP_NAME=Acme 覆盖仓库里写死的 defaultTemplateContext: { appName: 'Acme Dev' },发出去的品牌邮件仍然叫 Acme Dev。连带地,没有配 defaultFrom 时的兜底发件人也是从这个值 slug 化来的(no-reply@acme-dev.local),所以错的不止是正文。
两种读法(需要维护者定)
-
A:现状即正确 —— 「显式完整上下文」比「便捷单键 + env」更具体,应该赢。那就该在 TSDoc 里把
appName标为 env 规则的显式例外(spec: EmailServiceConfigSchema 未声明 CLI 实读的 queueDelivery / appName / defaultTemplateContext(与 #5104 同族,不同键) #5307 的 PR 已按这个读法把现状写清楚了),不改代码。 -
B:env 必须赢 —— 与文件声明的解析顺序一致,也与其余所有键一致。改法是把 env/键的解析结果放在展开之后:
const defaultTemplateContext = { ...(cfgEmail.defaultTemplateContext || {}), appName: env.OS_APP_NAME || cfgEmail.appName || (cfgEmail.defaultTemplateContext || {}).appName || configAppName || 'ObjectStack', };
注意这里要显式把
defaultTemplateContext.appName插进链条(排在两个专用来源之后、顶层configAppName之前),否则从 B 改过去会把「只写了defaultTemplateContext.appName、没写appName」的既有配置直接降级到'ObjectStack'—— 修一个静默错值换来另一个,更糟。
倾向 B:一份配置多环境部署时 env 覆盖是运维的唯一手段,而当前行为让这个手段在一个键上无声失效;例外规则(A)要求作者读到 TSDoc 的那一段才知道,属于「必须记住才不会踩」的设计。但 B 会改变既有部署的可观察行为(user-visible,需要 changeset),不是能顺手带的改动。
关联
- spec: EmailServiceConfigSchema 未声明 CLI 实读的 queueDelivery / appName / defaultTemplateContext(与 #5104 同族,不同键) #5307 补齐
appName/defaultTemplateContext声明的同一次实测;该 PR 在packages/cli/src/commands/serve-email-config-parity.contract.test.ts以spreads defaultTemplateContext OVER the resolved appName, as documented钉住现状并指回本单——若选 B,那条测试连同 schema TSDoc 一起改。 - Prime Directive [WIP] Create a new release version #9(
OS_APP_NAME是不合OS_{DOMAIN}_{FEATURE}形状的历史遗留变量,AGENTS.md 已把这类列为 debt;本单不涉及改名)
Activity
baozhoutao commentedon Aug 5, 2026
分诊(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh,2026-08-05):入队pm:queue,domain:cli。判级:真缺陷 —— 文件自述的解析顺序(「env 逐项覆盖」)对其余全部键成立,唯独appName静默失效,多环境部署时运维唯一手段无声作废。方向裁定(留否决窗口):取 B(env 必须赢) —— 判据不是口味而是既有契约:
EmailServiceConfigSchemaTSDoc 与生成文档把解析顺序写成承诺,其余所有键照此执行,B 是恢复该成文不变量;A 的「例外规则」属于必须读到 TSDoc 那段才不踩的设计(防 AI 轴反面)。已记录的接受代价:依赖 context-压-env 现状的既有部署行为会变(user-visible,changeset 写明)—— 维护者不接受此代价可否决改 A(届时只改 TSDoc 把例外标明)。执行细节照正文 B 的陷阱注记:defaultTemplateContext.appName显式插入链条(两个专用来源之后、顶层configAppName之前),防止把「只写了 context.appName」的配置降级到'ObjectStack';#5307 钉现状的那条 contract 测试连同 schema TSDoc 一起改。⛔ 串行约束(step 3 记录):与 #5447 同函数,#5447(enforce persist,PII 优先)先行,本单待其 MERGED 后基于新 main 派发。
Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
认领:PM 循环第 8 轮(cli 车道)
会话:
session_016FNvXhtSdnEGEfLEsMmvxh分支:
claude/issue-5448-appname-env-winsWorktree:
objectstack-issue-5448域:
domain:cli文件面:
packages/cli/src/commands/serve.ts(resolveEmailCapabilityArg的defaultTemplateContext解析)+packages/cli测试(含 #5307 分支的 contract 测试若已合入则同步改)。串行约束已解除:PR #5470(#5447)已 MERGEDcd2efe62a。与在飞 #5224(rest/runtime 面)不同文件;#5462 继续缓派。执行口径沿 12:3xZ 分诊裁定:方向 B(env 必须赢),链条按正文陷阱注记显式插入
defaultTemplateContext.appName(两个专用来源之后、顶层configAppName之前),防「只写 context.appName」的配置降级;changeset 写明行为变更;#5307 钉现状的测试与 schema TSDoc 若在 main 则一并改。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
验收(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh):ACCEPT → PR #5498。cfgEmail.appName>context.appName>configAppName> 默认),防降级陷阱兑现(仅写 context 形式不降级,且 env 仍能压过它);其它 context 键整体展开不变,并有「特判恰好一键宽」的钉宽断言 —— 这是防未来悄悄扩大特判面的正确形状。config.email.persist接线到 EmailServicePlugin,补上OS_EMAIL_PERSIST_ENABLED(#5447) #5470 的耦合通知同一口径,一致。CI 核后转 ready 挂 auto-merge,跟到 MERGED。
Generated by Claude Code
docs(spec): appName 文案与 parity pin 收口到 #5448 裁定的新序(PR #5498)
fix(cli): OS_APP_NAME overrides config.email.defaultTemplateContext.a…
feat(spec): EmailServiceConfigSchema 补齐 CLI 实读的 queueDelivery / appNa…