Skip to content

feat(spec): EmailServiceConfigSchema 补齐 CLI 实读的 queueDelivery / appName / defaultTemplateContext (#5307) - #5465

Merged
os-zhuang merged 7 commits into
mainfrom
claude/issue-5307-email-config-keys
Aug 5, 2026
Merged

feat(spec): EmailServiceConfigSchema 补齐 CLI 实读的 queueDelivery / appName / defaultTemplateContext (#5307)#5465
os-zhuang merged 7 commits into
mainfrom
claude/issue-5307-email-config-keys

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5307

前提复核(先于实施)

issue 的前提在 origin/main @ ed0d2aa仍然成立,#5308(#5104)昨日动过同一文件但只加了 provider 枚举值:

$ git show origin/main:packages/cli/src/commands/serve.ts | grep -oE "cfgEmail\.[a-zA-Z]+" | sort -u
cfgEmail.apiKey / appName / defaultFrom / defaultTemplateContext / options / provider / queueDelivery / retries

EmailServiceConfigSchema 当时声明:provider / apiKey / defaultFrom / retries / persist / options。差集正是 issue 说的三个。

改了什么

三个键都是运行时已经在读的,本次是把契约追平既成事实,运行时零改动:

形状 读侧实测语义
queueDelivery z.boolean().optional() #5160 的耐久队列投递开关。env OS_EMAIL_QUEUE_ENABLED 覆盖;retries 复用为队列尝试预算;无 queue 服务或 persist: false 时在 kernel:ready 硬失败
appName z.string().optional() 模板产品名。OS_APP_NAME → 本键 → 顶层 config.appName'ObjectStack';无 defaultFrom 时兼作兜底发件人来源(no-reply@acme-crm.local)
defaultTemplateContext z.record(z.string(), z.unknown()).optional() 合并进每次 sendTemplate() 的渲染上下文。读侧原样透传

两个刻意的克制:

  • 不给 .default() 默认值由 resolveEmailCapabilityArg 对着 env 与顶层 config 解析,schema 再造一个只会多出一个谁也不赢的答案。
  • defaultTemplateContext 保持自由 record。 读侧只做透传,声明一套读侧没有的约束等于发明契约(PM notes 的要求,也是 Prime Directive Add comprehensive test suite for Zod schema validation #12 的方向)。

新增的守护:把 issue 那条手工 grep 机械化

packages/cli/src/commands/serve-email-config-parity.contract.test.ts —— 这一族缺陷(#5104、本单)两次都是人肉 grep 发现的。该测试把读侧源码里的 cfgEmail.<key>EmailServiceConfigSchema.shape 对齐,两个方向都断言:

另加行为半边:用真 schema parse() 一份作者配置,把结果喂给真 resolveEmailCapabilityArg,断言三个值确实抵达插件选项 —— 只比名字不够,schema 可能用一个读侧根本不认的形状声明同名键。

反向验证(方向先定,再跑)

预期:恢复到 origin/main 的 schema 后,新增的钉子全部转红。实测符合,并多出一条没预料到的信号:

  1. spec 侧 6 条新用例 5 红:expected undefined to be true(键被 strip 掉了)等。第 6 条 leaves all three absent when unwritten 恢复后仍绿 —— 它守的是「将来别加 .default()」,不是本次修复的红/绿检测器,这里如实记下而不是凑成 6/6。
  2. cli 侧 6 条 5 红,其中 declares every config.email key the resolver reads 报的正是 expected [ 'appName', …(2) ] to deeply equal [],与注释里写的三个键一字不差。仍绿的那条是 persist 豁免断言 —— 它守 spec/cli: EmailServiceConfig.persist 声明了但没有载体 —— config.email.persist 永远到不了 EmailServicePlugin(#5307 的反向面,ADR-0049) #5447,与本单无关。
  3. 意外的第三条(更强):只回滚 schema、不回滚 authorable-surface.json,pnpm --filter @objectstack/spec build 直接失败 —— check:authorable-surface 是删除棘轮,已记录的三个键突然消失即触发它的 tombstone 流程。生成物这一半也是被钉住的,不只是随行产物。

生成物(#4001 纪律)

  • packages/spec/authorable-surface.json +3(gen:schema)。注:该 gate 是删除棘轮,新增键若不记入就「对它永久隐形」,所以必须随行。
  • content/docs/references/system/email-config.mdx 重新生成,属性表出现三行。
  • pnpm gen:strictness-ledger 整体重算,零 diff —— 台账只分诊 ui/data/automation/security/studio,system/ 不在其内,且往既有 z.object( 加键不改变 site 数。如实报告为「重算过、无变化」,没有手改任何数字。
  • check:generated 九个 gate 全绿。

顺带发现(均已另立单,本 PR 不修)

验证

pnpm --filter @objectstack/spec check:generated   → All 9 generated artifacts are up to date
pnpm --filter @objectstack/spec typecheck         → Done
pnpm --filter @objectstack/cli  typecheck         → Done
pnpm --filter @objectstack/spec test              → 310 files / 7940 tests passed
pnpm --filter @objectstack/cli  test              → 80 files / 773 tests passed
node scripts/check-nul-bytes.mjs                  → OK (5443 files)

已 merge origin/main(61fde5e),与来件零文件重叠;合并后 check:generated 复跑仍全绿,origin/main...HEAD 的 delta 恰为 6 个文件、394 行纯新增、零删除。


Generated by Claude Code

claude added 3 commits August 5, 2026 12:15
…me / defaultTemplateContext (#5307)

config.email 在全仓只有一个读者:packages/cli/src/commands/serve.ts 的
resolveEmailCapabilityArg。它读八个键,schema 只声明五个,差集三个已经被
运行时消费多时 —— queueDelivery(#5160 的耐久队列开关)、appName(模板
产品名 + 兜底发件人来源)、defaultTemplateContext(自由渲染上下文)。

与 #5104 完全同型的 declared != implemented,spec 在落后的一侧:用
EmailServiceConfig 标注 objectstack.config.ts 的作者写 queueDelivery: true
会拿到类型错误,而同一份配置起得来、也确实走队列。

- 三个键均为 optional 且不带 .default():默认值由 resolveEmailCapabilityArg
  对着 env 与顶层 config 解析,schema 再造一个只会多出一个谁也不赢的答案
- defaultTemplateContext 保持自由 record,读侧原样透传,不发明约束
- TSDoc 写清语义、默认值与优先级,含 defaultTemplateContext.appName 压过
  OS_APP_NAME 这一处实测到的例外(另立 #5448)
- 新增跨包契约测试把 issue 的手工 grep 机械化:读侧新增未声明键即变红,
  persist 作为唯一 DECLARED_BUT_UNREAD 豁免登记并指回 #5447

运行时零改动。
- authorable-surface.json 记入三个新可授权键(gen:schema;check:authorable-surface
  是删除棘轮,新增键不记入即"对该棘轮永久隐形")
- content/docs/references/system/email-config.mdx 重新生成,属性表出现三行
- appName 的 .describe() 不再写 {{appName}}:生成器会把双花括号转义成
  `{{x}` 加一个游离的 },main 上已有 3 处同样的破损(已另立 #5452),
  源码留注释说明为何这里绕开

check:generated 九个 gate 全绿;gen:strictness-ledger 整体重算零 diff
(台账只分诊 ui/data/automation/security/studio,system/ 不在其内)。
@vercel

vercel Bot commented Aug 5, 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 5, 2026 3:19pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests tooling size/m labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31010154371 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/commands/serve-email-config-parity.contract.test.ts�[2m > �[22mEmailServiceConfigSchema ↔ resolveEmailCapabilityArg�[2m > �[22mreads every key it declares, but for the fi
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 4 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

分诊(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D):已定因,不重排,修复在途。

⚠️ 给 merge-queue-triage 的同类评论检索者:如在其它 PR 看到 serve-email-config-parity.contract.test.ts 同名失败,先查该 PR 是否携带此测试的旧豁免版本,不要计入 flaky 统计。


Generated by Claude Code

`serve-email-config-parity.contract.test.ts` 曾注册唯一一条 `DECLARED_BUT_UNREAD`
豁免:`persist` 被 schema 声明却无人读取(#5447)。cli 车道的 PR #5470
(`cd2efe62a`)已合入 main —— `resolveEmailCapabilityArg` 现在经由新的
`envBooleanFlag` / `OS_EMAIL_PERSIST_ENABLED`(三态,默认仍为 ON)读取
`cfgEmail.persist`。

于是本分支的断言在 merge queue 里必然变红:它 expect `unread == ['persist']`,
而合并后的 main 上 `unread == []`。跨车道协议(#5447 评论)约定后落地方对齐,
即本分支。

对齐做法:删除豁免数组,而非留一个空数组 —— 空注册表是一种邀请,下一个
declared-but-unread 键会被直接追加进去而不必辩论,正是该条目当初要防止的
「静默豁免」。断言随之收紧为两个方向都为空,即 declared 集与 read 集相等,
也就是文件注释当初许诺的 plain set equality。豁免的来龙去脉保留在注释中。

反向验证(方向预先判定为 red,结果一致):保留旧豁免数组对合并后的 main 运行,
`AssertionError: expected [] to deeply equal [ 'persist' ]` —— 这正是本次预先
规避的队列失败;撤销豁免后该文件 6 个用例全绿。

另:changeset 里「它读八个键」是 #5470 之前的读侧计数(现为九个),补时间
限定词「本次改动时」,以免这段 CHANGELOG 文案落地后失真。schema 中 `persist`
的 TSDoc「Persist to sys_email (default true)」经核对在 #5470 之后依然成立
(默认仍为 ON),故不改。

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

Copy link
Copy Markdown
Contributor Author

跨车道对齐:撤销 persist 豁免(#5447 已由 PR #5470 落地)

上面正文里「声明侧有、读侧没有 → 只允许 persist 一项,登记在 DECLARED_BUT_UNREAD……#5447 落地后该数组清空、断言收紧为集合相等」这句已经兑现,本条记录兑现过程,正文不再改写(它已经过一次 GitHub 正文消毒,重提交会二次损坏)。

触发:cli 车道 PR #5470(合并提交 cd2efe62a,"fix(cli): carry config.email.persist to EmailServicePlugin (#5447)")已合入 main。它让 resolveEmailCapabilityArg 经由新的 envBooleanFlag 助手与 OS_EMAIL_PERSIST_ENABLED(三态,默认 ON)读取 cfgEmail.persist,但没有动 parity 测试 —— 那个文件只存在于本分支。按 #5447 评论里记录的跨车道协议,后落地方对齐,即本分支。

为什么必须现在做:本分支的断言 expect(unread).toEqual(['persist']) 在 merge queue 的「以合并后 main 重建」里必然变红,PR 会被踢出队列。

反向验证(方向先判定为 red,实测一致):保留旧豁免数组,对合并后的 main 跑该文件 ——

FAIL  src/commands/serve-email-config-parity.contract.test.ts
  > reads every key it declares, but for the filed exemption (#5447)
AssertionError: expected [] to deeply equal [ 'persist' ]
 Test Files  1 failed (1) | Tests  1 failed | 5 passed (6)

这正是预先规避的队列失败。撤销豁免后该文件 6 条全绿。

做法:豁免数组删除,而不是留成空数组 —— 空注册表是一种邀请,下一个 declared-but-unread 键会被直接追加进去而不必辩论,恰是该条目当初要防止的「静默豁免」;要再豁免就得连机制一起重新引入,写在一个需要有人辩护的 diff 里。断言随之为两个方向都为空,即 declared 集与 read 集相等。豁免的来龙去脉(PII 影响面、ADR-0049 answered "enforce"、#5470 / cd2efe62a)保留在注释里。

顺带核对:schema 里 persist 的 TSDoc「Persist to sys_email (default true)」在 #5470 之后依然成立(默认仍为 ON,env 与 config 都不写时该键不进构造选项,由插件默认决定),故不改。changeset 里「它读八个键」是 #5470 之前的读侧计数(现为九个),补了时间限定词「本次改动时」。

验证(均在 merge 后、合并 main 之上跑):

pnpm --filter @objectstack/cli  test            → 81 files / 786 tests passed
pnpm --filter @objectstack/spec test            → 310 files / 7940 tests passed
pnpm --filter @objectstack/spec check:generated → All 9 generated artifacts are up to date
pnpm --filter @objectstack/cli --filter @objectstack/spec typecheck → Done
node scripts/check-nul-bytes.mjs                → OK (5457 files, whole C0 set)

merge 为 git merge origin/main,零冲突、无 os-regen-pending;origin/main...HEAD 的 delta 仍恰为本单的 6 个文件,三条 system/EmailServiceConfig: 新键(appName / defaultTemplateContext / queueDelivery)在 authorable-surface.json 中完好,main 侧无丢失。

未做(避免扩大范围):OS_EMAIL_PERSIST_ENABLED 的文档化。queueDelivery 的 TSDoc 点名了 OS_EMAIL_QUEUE_ENABLED,而 persist 的没有点名它那一半 env 层,生成的参考文档因此也不提 —— 属 #5470 的收尾面,留给 PM 决定是否另立单。


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31015301925 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/commands/serve-email-config-parity.contract.test.ts�[2m > �[22ma config the schema accepts reaches the plugin intact�[2m > �[22mspreads defaultTemplateContext OVER the re
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Copy link
Copy Markdown
Contributor Author

分诊(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D):第二次语义互锁,已定因,不重排,对齐在途。非 flaky —— 请勿计入 flaky 统计。

给 merge-queue-triage 检索者:本 PR 24h 内两次队列失败都是已定因的跨车道语义合并冲突(persist 接线 / appName 优先级),零 flaky 成分。


Generated by Claude Code

#5448 已裁 direction B 并由 PR #5498 落地:`resolveEmailCapabilityArg` 现在把
`appName` 放在 context 展开之后解析,五级链为 `OS_APP_NAME` >
`config.email.appName` > `defaultTemplateContext.appName` > 顶层 `appName` >
`'ObjectStack'`。本分支上写于旧序之上的三处东西随之收口:

- `serve-email-config-parity.contract.test.ts` 那条 pin 原本钉的是旧序
  (context.appName 压过 env),现改为钉新序。它保留本文件自己的角度而非
  重述 #5498 的用例:配置先过真正的 `EmailServiceConfigSchema.parse()` 再喂
  读侧,因此钉住的是 #5307 新加的两个契约键既能存活 parse、又确实落在
  schema 文案承诺的档位上。
- `email-config.zod.ts` 中 `appName` / `defaultTemplateContext` 的 TSDoc 与
  两处 `.describe()`:旧文案写的是「写在 context 里的 appName 压过 appName
  键与 OS_APP_NAME、是否合理 filed as #5448」,该事实已不成立。

`email-config.mdx` 由 `gen:docs` 整体重生成(未手改),9 个 generated 门全绿。
运行时零改动 —— `serve.ts` 未被本次改动触碰。

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

Copy link
Copy Markdown
Contributor Author

对齐轮 2:收口到 #5448 已裁定的新序(70ad09d)

已 merge origin/main(736519d,含 cli 车道的 PR #5498),并把本分支上写于旧序之上的东西全部收口。PR 正文「顺带发现」里 #5448 那条 —— 「本 PR 只把实测到的现状写进 TSDoc 并用测试钉住,不改行为」 —— 自 #5498 合入起已过期,以本评论为准。

#5448 的结局

direction B,由 PR #5498 落地:resolveEmailCapabilityArg 现在把 appName 放在 context 展开之后解析,五级链为

OS_APP_NAME > config.email.appName > config.email.defaultTemplateContext.appName > 顶层 config.appName > 'ObjectStack'

defaultTemplateContext其余键一律不变,仍整体展开透传 —— 特例恰好一个键宽。serve.ts 本次未被触碰,运行时零改动。

本次改了三处

  1. parity 测试那条 pin。原本钉的是旧序(spreads defaultTemplateContext OVER the resolved appName, as documented,断言 'From The Context'),正是队列重建失败的那条。改为钉新序,并保留本文件自己的角度而非重述 fix(cli): OS_APP_NAME 压过 config.email.defaultTemplateContext.appName —— 恢复「env 逐项覆盖」契约 (#5448) #5498:配置先过真正的 EmailServiceConfigSchema.parse() 再喂读侧,因此钉住的是 spec: EmailServiceConfigSchema 未声明 CLI 实读的 queueDelivery / appName / defaultTemplateContext(与 #5104 同族,不同键) #5307 新加的两个契约键既能存活 parse、又确实落在 schema 文案承诺的档位上 —— schema 若改名/strip/换形状,这里会红而 fix(cli): OS_APP_NAME 压过 config.email.defaultTemplateContext.appName —— 恢复「env 逐项覆盖」契约 (#5448) #5498 自己的用例仍绿。
  2. email-config.zod.ts 的文案appName 的 TSDoc(旧文写「setting it here is exactly defaultTemplateContext: { appName: … } with the env layer in front」—— 新序下两者不再等价,本键是更高一档)、defaultTemplateContext 的 TSDoc(整段「context 的 appName 压过 env、filed as cli: config.email.defaultTemplateContext.appName 压过 OS_APP_NAME —— 与「env 逐项覆盖」的声明相反 #5448」)、以及两处 .describe()(含 An appName written here overrides both the appName key and OS_APP_NAME)。
  3. changeset 补一句:describe 文案随新序变化,故生成的属性表两行同步更新。

email-config.mdxpnpm --filter @objectstack/spec gen:docs 整体重生成(未手改);239 个生成文件中只有它变化,且只有那两行 describe。

先证红(方向先定,再跑)

预测:把 serve.ts 临时还原旧展开顺序 → 本 pin 必红,rung 1 答 'From The Context' 而非 'From The Env';rung 3(anti-demotion)两序皆绿,不具判别力。

实测与预测一致:

FAIL  serve-email-config-parity.contract.test.ts > resolves appName by the five-rung chain (#5448)
AssertionError: expected { appName: 'From The Context', …(1) } to deeply equal { appName: 'From The Env', …(1) }
-   "appName": "From The Env"
+   "appName": "From The Context"

rung 2 因断言在 rung 1 即中止而未跑到,单独探针补证它同样具判别力(旧序下无 env 时也答 'From The Context',而非本键的 'From The Key'):

rung2 (no env, key + context both present) -> "From The Context"

三个值刻意互不相同 —— 断言一个三者一致的值在任何序下都会绿。随后 serve.ts 已还原,git status 中不含该文件。

验证

pnpm --filter @objectstack/spec check:generated  → All 9 generated artifacts are up to date
pnpm --filter @objectstack/spec test             → 312 files / 7968 tests passed
pnpm --filter @objectstack/cli  test             → 82 files / 798 tests passed
  ├ parity 6 + #5498 precedence 12               → 18 passed
  └ serve-email-persist.test.ts (#5470)          → 13 passed
pnpm --filter @objectstack/spec typecheck        → Done
pnpm --filter @objectstack/cli  typecheck        → Done
node scripts/check-nul-bytes.mjs                 → OK (5470 files)

未改 draft/ready 状态,未合并。


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 667fa44 Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5307-email-config-keys branch August 5, 2026 16:00
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 protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: EmailServiceConfigSchema 未声明 CLI 实读的 queueDelivery / appName / defaultTemplateContext(与 #5104 同族,不同键)

2 participants