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

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

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.

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

2 participants