Blocked-by: objectstack-ai/objectstack#5161(同包串行:#5160 → #5161 → 本单;#5161 在 email-plugin.ts,本单主体在 email-service.ts + platform-objects,文件面基本不相交,但同包不同批) 维护者两次拍板(2026-08-04,会话内): 1. #5172 按「headers 先行、附件走 storage 引用」拆分; 2. 追加:**邮件队列先不支持大附件** —— 小附件设上限内联进行,大附件维持退回内联投递;storage 引用降为二期(#5172,已移出派发队列)。 本单因此覆盖两块同形改动(同三个文件里的加列 + 重建 + 资格判定,拆开纯属自我串行): ## A. headers_json(终态方案) 1. `sys-email.object.ts` 加 `headers_json`(可空 JSON 文本;老行读回 `undefined` 必须安全); 2. `send()` 落行时持久化 `headers`(两种模式都落,兼有审计价值); 3. `rowToNormalized()` 重建 `headers`; 4. headers 不再是退回内联的理由。 ## B. attachments_json(一期:有界内联,schema 一次到位) 1. `sys-email.object.ts` 加 `attachments_json`,元素形状**现在就设计成二期可扩展**: ``` { filename, contentType, size, hash, inline?: string /* base64 */, storageKey?: string } ``` 一期只有 `inline` 有生产者;`storageKey` 是二期(#5172)的扩展点 —— 将来支持大附件是给已声明字段补生产者,不动 schema、不迁移数据。字段本身要有 TSDoc 说明 `storageKey` 现阶段无生产者、由 #5172 跟踪,免得被 liveness 扫描误判。 2. **上限:整封合计 256KB 原始字节(PM 默认,dev 可凭实测理由调整并在 PR 说明)**;写成导出常量,不做配置项——少一个旋钮少一处漂移,有实证需求再提升为设置。 3. 资格判定:`≤ 上限` → 入队(内容 base64 进 `inline`);`> 上限` → **维持现行为**(退回内联投递 + info 说明,理由文案更新为「附件合计超过 X」)。超限不是错误,最坏结果=今天的现状。 4. `rowToNormalized()` 重建附件(含 `cid`——检查 `EmailAttachment.cid` 是否也要进元素形状,应该要,内联图片依赖它); 5. `Buffer` 与 `string` 两种 `content` 形态都要正确编码/还原(契约是 `string | Buffer`)。 ## 不在本单 - 大附件 storage 引用 + 内容随终态回收 = #5172(二期,维护者定为暂缓,勿动); - ⛔ `packages/spec` 无需动(`headers`/`attachments` 都在契约上)也不许动;⛔ `content/docs/releases/`。 ## 验收 - `queueDelivery` 开 + `send({ headers })` → `queued`,worker 投递的消息含全部自定义 header; - `queueDelivery` 开 + 小附件(≤上限,含中文文件名、`cid` 内联图、Buffer 与 base64 两种 content)→ `queued`,worker 投递的消息附件完整还原(真 `DbQueueAdapter` + wire 测试断言字节一致); - 大附件(>上限)→ 退回内联 + info,文案说明超限,投递内容完整不剥附件; - 老行(两列皆无)读取安全;默认(内联)模式行为不变,既有断言零改动; - `sys_email` 行体积有界:附件列最坏 ~350KB(256KB 的 base64),有一条用例断言超限内容**不会**落列。
Activity
[-]plugin-email/platform-objects: sys_email 加 headers_json —— 自定义 header 邮件取得队列投递资格(#5172 拆分,终态方案)[/-][+]plugin-email/platform-objects: sys_email 加 headers_json + 有界 attachments_json —— 小附件与自定义 header 取得队列投递资格(#5172 一期)[/+]os-zhuang commentedon Aug 4, 2026
认领:PM 循环第 7 轮
会话:
session_017MCKJaEomEqg4tvz4SzdNd分支:
claude/issue-5177-sys-email-headers-attachmentsWorktree:
objectstack-issue-5177域:
domain:services(跨域声明:主体在 plugin-email,加列在 platform-objects)文件面:
packages/plugins/plugin-email/src/、packages/platform-objects/src/audit/sys-email.object.ts(越界即停,报告说明)Blocked-by 已解除:#5161 的 PR #5191 已合入(
d25f20b69),落地判据核过。与在飞的 #5193(plugin-audit)、#5195(objectql)不同包,并批安全。Generated by Claude Code
fix(platform-objects): 补上 sys_email 两个新列的 i18n bundle (#5177)
os-zhuang commentedon Aug 4, 2026
复核结论:ACCEPT —— PR #5211,转 ready 并入合并队列。head 上 23 个 check run 全部 success/skipped。
接手过程与真因
原 dev 推送后被会话用量终止(零报告)。接手 agent 的诊断值得记录:「TypeScript Type Check」job 的红根本不是类型错误 —— 完整日志显示
tsc119/119 全过,失败的是同 job 里后置的pnpm check:i18n:给sys-email.object.ts加两列(各带 label/description)改变了 i18n schema,四份生成 bundle 未再生成。修复945935ed6只动四份 bundle,并顺手把两个新键翻译到 zh-CN/ja-JP/es-ES(merge 模式只回填源文,而 sys_email 其余字段三语全有——不补就是新的不一致)。这是今天第三次「job 名 ≠ 真因」,取完整日志的纪律又对了一次。接手 agent 还按协议逐条核了分支实现对 #5177 规格的符合性(原 agent 零证据):超限不落列有直接断言、上限在边界处含等号且按整封求和、
smtp.wire.test.ts的抽取是纯机械搬移(同 3 测试 16 断言)。对留裁的形状偏差:接受,理由如下
持久化元素形状与 issue 字面规格有两处偏差,均予采纳:
contentType可选 —— 与EmailAttachment.contentType?: string契约一致;issue 形状没标可选性是我写漏,强制必填反而要在持久化层发明一个契约上不存在的约束。contentForm——content: string | Buffer两种形态的无损往返需要这个判别子:没有它,重建时无从知道该还原成 string 还是 Buffer,而两者经 nodemailer 的传输编码路径不同。这是把「往返无损」从愿望变成结构保证,符合本单的全部要义。该形状是
sys_email列内 JSON(内部持久化面),不是公共 spec 面,属 PM 可裁范围;若维护者异议可否决重开。衍生 #5217(check-i18n-bundles 把「CLI 未构建」误报成 9 个 bundle 问题,误导本地复现的第一步)已按观察类标
finding,留分诊轮。至此邮件线一期收尾:headers 与 ≤256KB 附件获得队列投递资格,
storageKey扩展点就位,#5172(二期)按维护者拍板继续暂缓。Generated by Claude Code
feat(plugin-email,platform-objects): sys_email 加 headers_json + 有界 at…
feat(plugin-email): large attachments go to storage, so queued delive…
docs(plugin-email): re-anchor the dead tracker citations to the commi…