实施 #5307 (补齐 EmailServiceConfigSchema 未声明的三个实读键)时,按该 issue「待确认」一节核实了反向的 persist 键。结论:它是 declared-but-unenforced,不是「在别处被消费」 。本单只记录,#5307 的 PR 未动它。
实测(origin/main @ ed0d2aa )
config.email 在全仓只有一个读者:
grep -rn " config as any).email\|config\.email\b" --include=* .ts --exclude-dir=node_modules --exclude-dir=dist packages/ examples/ | grep -v " \.test\.ts"
# packages/cli/src/commands/serve.ts:2335: (config as any).email ?? {}, <-- 唯一读者
即 packages/cli/src/commands/serve.ts 的 resolveEmailCapabilityArg。它读的键(#5307 的复现命令):
cfgEmail.apiKey / appName / defaultFrom / defaultTemplateContext / options / provider / queueDelivery / retries
没有 cfgEmail.persist。 该函数拼出的 options 对象里也没有 persist 字段。
为什么不是「在 plugin 侧被消费」
插件选项 persist 本身是活的:
packages/plugins/plugin-email/src/email-plugin.ts:81 声明 persist?: boolean
同文件 482 行按它决定是否构造 EmailPersistence:
const persistence: EmailPersistence | undefined = this.options.persist === false ? undefined : { ... }
877 行还用它写诊断:sys_email persistence is disabled ('persist: false'), so a queued job would have no row to deliver
活的是插件构造参数 。缺的是从 config.email.persist 到那个构造参数的那一段路 ——resolveEmailCapabilityArg 是唯一能走这一段的代码,而它不读这个键。settings 侧(Settings → Mail)也没有对应开关(email-plugin.mail-settings.test.ts 与 mail-manifest-providers.contract.test.ts 均无 persist)。
影响
作者在 objectstack.config.ts 写:
email: { provider : 'smtp' , persist : false , options : { host : 'smtp.acme.test' } }
用 EmailServiceConfig 标注 —— 类型通过,schema safeParse 通过,生成的参考文档 content/docs/references/system/email-config.mdx 正面写着「Persist to sys_email (default true)」。运行时依旧把每一封邮件写进 sys_email。
这正是 Prime Directive #10 的 declared ≠ enforced:一个 PII 敏感部署以为自己关掉了邮件正文落库,其实没有。方向与 #5307 相反(那边是 spec 落后于运行时,这边是 spec 超前于运行时),所以拆成两单。
待决(ADR-0049 enforce-or-remove,两条路)
enforce —— 在 resolveEmailCapabilityArg 里补 ...(cfgEmail.persist != null ? { persist: !!cfgEmail.persist } : {}),并配一个 OS_EMAIL_PERSIST_ENABLED(Prime Directive [WIP] Create a new release version #9 的布尔开关形状)。成本约一行 + 测试;把已声明的契约兑现。
remove —— 从 EmailServiceConfigSchema 删掉 persist,走 spec-property-retirement 的整套纪律(ADR-0087 转换层 / liveness 台账 / 生成物)。代价高,而且删掉的是一个能力真实存在、只是没接线 的键,直觉上不对。
倾向 1(enforce):插件侧已经完整实现并有诊断文案,缺的只是配置通路;删声明等于把一个真能力藏起来,只能通过直接 new EmailServicePlugin({ persist: false }) 触达——而 os serve 路径下作者根本没有这个入口。
关联
实施 #5307(补齐
EmailServiceConfigSchema未声明的三个实读键)时,按该 issue「待确认」一节核实了反向的persist键。结论:它是 declared-but-unenforced,不是「在别处被消费」。本单只记录,#5307 的 PR 未动它。实测(origin/main @ ed0d2aa)
config.email在全仓只有一个读者:即
packages/cli/src/commands/serve.ts的resolveEmailCapabilityArg。它读的键(#5307 的复现命令):没有
cfgEmail.persist。 该函数拼出的options对象里也没有persist字段。为什么不是「在 plugin 侧被消费」
插件选项
persist本身是活的:packages/plugins/plugin-email/src/email-plugin.ts:81声明persist?: booleanEmailPersistence:const persistence: EmailPersistence | undefined = this.options.persist === false ? undefined : { ... }sys_email persistence is disabled ('persist: false'), so a queued job would have no row to deliver活的是插件构造参数。缺的是从
config.email.persist到那个构造参数的那一段路——resolveEmailCapabilityArg是唯一能走这一段的代码,而它不读这个键。settings 侧(Settings → Mail)也没有对应开关(email-plugin.mail-settings.test.ts与mail-manifest-providers.contract.test.ts均无 persist)。影响
作者在
objectstack.config.ts写:用
EmailServiceConfig标注 —— 类型通过,schemasafeParse通过,生成的参考文档content/docs/references/system/email-config.mdx正面写着「Persist to sys_email (default true)」。运行时依旧把每一封邮件写进sys_email。这正是 Prime Directive #10 的 declared ≠ enforced:一个 PII 敏感部署以为自己关掉了邮件正文落库,其实没有。方向与 #5307 相反(那边是 spec 落后于运行时,这边是 spec 超前于运行时),所以拆成两单。
待决(ADR-0049 enforce-or-remove,两条路)
resolveEmailCapabilityArg里补...(cfgEmail.persist != null ? { persist: !!cfgEmail.persist } : {}),并配一个OS_EMAIL_PERSIST_ENABLED(Prime Directive [WIP] Create a new release version #9 的布尔开关形状)。成本约一行 + 测试;把已声明的契约兑现。EmailServiceConfigSchema删掉persist,走 spec-property-retirement 的整套纪律(ADR-0087 转换层 / liveness 台账 / 生成物)。代价高,而且删掉的是一个能力真实存在、只是没接线的键,直觉上不对。倾向 1(enforce):插件侧已经完整实现并有诊断文案,缺的只是配置通路;删声明等于把一个真能力藏起来,只能通过直接
new EmailServicePlugin({ persist: false })触达——而os serve路径下作者根本没有这个入口。关联
packages/cli/src/commands/serve-email-config-parity.contract.test.ts里把persist登记为唯一的DECLARED_BUT_UNREAD豁免项并指回本单;本单落地后那个数组清空,断言收紧为集合相等)