Repository navigation
cli: config.email.defaultTemplateContext.appName 压过 OS_APP_NAME —— 与「env 逐项覆盖」的声明相反 #5448
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on 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
认领:PM 循环第 8 轮(cli 车道)
会话:session_016FNvXhtSdnEGEfLEsMmvxh
分支:claude/issue-5448-appname-env-wins
Worktree: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
验收(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh):ACCEPT → PR #5498。- 方向 B 按裁定精确落地:五级链条(env >
cfgEmail.appName>context.appName>configAppName> 默认),防降级陷阱兑现(仅写 context 形式不降级,且 env 仍能压过它);其它 context 键整体展开不变,并有「特判恰好一键宽」的钉宽断言 —— 这是防未来悄悄扩大特判面的正确形状。 - 反向验证是分裂型预判(6 红 6 绿,红绿两集各自有因)且实测逐项命中 —— 比全红预判更有判别力。
- e2e 两例起真实 EmailServicePlugin 读活服务的 context,不拿键名钉键名;12 例 + 全包 792 绿、typecheck、NUL(含 0x7f 扩面)自扫齐备。
- spec: EmailServiceConfigSchema 未声明 CLI 实读的 queueDelivery / appName / defaultTemplateContext(与 #5104 同族,不同键) #5307 未合入 main 的实况如实核明(其 contract 测试与 schema 声明都不在基线上),不碰其分支,收口义务在 PR 正文标注为其后合闭环 —— 与 fix(cli):
config.email.persist接线到 EmailServicePlugin,补上OS_EMAIL_PERSIST_ENABLED(#5447) #5470 的耦合通知同一口径,一致。 - changeset 写明行为变更与接受代价(维护者否决窗口照旧:不接受 env-wins 可回 A,届时只改 TSDoc)。
CI 核后转 ready 挂 auto-merge,跟到 MERGED。
Generated by Claude Code
- 方向 B 按裁定精确落地:五级链条(env >
- added a commit that references this issue
on Aug 5, 2026
实施 #5307 时实测
resolveEmailCapabilityArg的模板上下文解析顺序,发现一处与文档声明相反的优先级。#5307 的 PR 只把实测到的现状写进 TSDoc 并用测试钉住,没有改行为——改不改是契约决定,故单独立单。实测(origin/main @ ed0d2aa)
packages/cli/src/commands/serve.ts,resolveEmailCapabilityArg内:appName先算,defaultTemplateContext再整体展开覆盖在它上面。于是:config.email.defaultTemplateContext.appNameOS_APP_NAME(环境变量)config.email.appNameconfig.appName复现(无需起服务):
为什么是问题
EmailServiceConfigSchema的头部 TSDoc(也是生成文档content/docs/references/system/email-config.mdx的开头)写的是:「环境变量逐项覆盖配置」是这个文件对所有键的承诺,
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/键的解析结果放在展开之后:
注意这里要显式把
defaultTemplateContext.appName插进链条(排在两个专用来源之后、顶层configAppName之前),否则从 B 改过去会把「只写了defaultTemplateContext.appName、没写appName」的既有配置直接降级到'ObjectStack'—— 修一个静默错值换来另一个,更糟。倾向 B:一份配置多环境部署时 env 覆盖是运维的唯一手段,而当前行为让这个手段在一个键上无声失效;例外规则(A)要求作者读到 TSDoc 的那一段才知道,属于「必须记住才不会踩」的设计。但 B 会改变既有部署的可观察行为(user-visible,需要 changeset),不是能顺手带的改动。
关联
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 一起改。OS_APP_NAME是不合OS_{DOMAIN}_{FEATURE}形状的历史遗留变量,AGENTS.md 已把这类列为 debt;本单不涉及改名)