Skip to content

Latest commit

 

History

History
1072 lines (730 loc) · 62.9 KB

plan-20260802.md

File metadata and controls

1072 lines (730 loc) · 62.9 KB

Website 内部产品邮件 API 计划(2026-08-02)

路径说明(2026-09-20): 本文成稿时 docker-compose-storage-only.yml / docker-compose-storage-only.auth-none.yml / docker-compose.test.yml 位于仓库根;2026-09-20 起三者迁入 docker/ 目录(commit 1854cae)。文中根目录原路径为历史记录,不再逐一回改;实际使用时请替换为 docker/ 前缀路径。

改名註記(2026-09-17): 本文成稿與執行時,Cargo 包名/二進制名為舊名,lib 為舊名 _core;2026-09-17 起全倉統一改名為 Mega2(mega2 / mega2_core),本文中的命令、路徑、環境變量與服務名已同步更新,歷史提交記錄本身不變。

模板版本: v2(成稿符合 docs/plan/plan-template.md v2)

文档职责

本文解决 website 尚未提供 POST /api/internal/notifications/email 的缺口(plan-20260731.md 的 DEP-06)。mega2 侧客户端、触发器接线与 compose bearer 占位已由 MN-05/MN-06 落地,但 website 仓仍只有认证事务邮件(@libs/email),无内部产品通知 API、无产品事件模板、无幂等受理。目标是:

  1. 在 website mega2 分支的 apps/next-app 实现与 docs/refactoring/website-mail.md 一致的内部邮件接口;
  2. 用 @libs/email 扩展产品通知模板,覆盖 mega2 现行五类产品事件;
  3. 提供 IT 可验证的投递路径(测试 provider 和/或 SMTP→mailpit),使栈级 compose 不再只能依赖 mega2 侧 wire mock;
  4. 在 mega2 仓 关闭 DEP-06、同步文档与(若需)compose/CI pin,并留下可继承的验收证据。

本文只规划任务,不宣称实现完成。落地时每个任务都必须先刷新源码锚点,再按任务卡验收。

适用范围

  • Website(主写集): apps/next-app/app/api/internal/notifications/email/、@libs/email 产品模板与(若需)smtp/测试 provider、bearer 校验 helper、单元/路由测试、website 侧 AGENTS/README 短说明。
  • Mega2(协调写集): docs/refactoring/website-mail.md / notification.md / test-infra.md / integration.md / docs/plan/plan-long.md / docs/plan/README.md;必要时 docker-compose.test.yml(website-next SMTP/provider env)、.github/workflows/config-validation.yml 的 website pin、可选 bin/tests 栈级邮件 IT。
  • 契约: 请求/响应/错误码/鉴权/幂等以 website-mail.md 为单一事实源;本计划可修订该契约,但必须同卡同步 mega2 客户端期望。

非目标

  • 不在 mega2 恢复 SMTP / [mail] / email_jobs(ADR-WA-08 不变)。
  • 不把 CL/issue 事件编排迁到 website;本计划只实现 投递 API。
  • 不实现 website 侧持久 outbox / 跨进程重试队列(见 DEFER-WE-01);本计划幂等为「受理去重」。
  • 不实现 SendGrid/Mailchimp;生产仍用已有 Resend/Cloudflare。
  • 不做 PT-12 全量前端对照;不改 Better Auth 会话路径。
  • 不强制本计划内替换 mega2 单元测试中的 wire mock(可并行保留);栈级绿依赖真实 API。

成功定义

  • website 暴露 POST /api/internal/notifications/email:Bearer 鉴权、必填 Idempotency-Key、JSON 形状与错误码符合 website-mail.md。
  • 五类事件(cl.comment.created / cl.merged / issue.comment.created / issue.closed / item.referenced)均有产品模板;未知 event_type → 422 unsupported_event。
  • 相同 Idempotency-Key 重放返回同一 delivery_id 且 duplicate: true,不重复发送。
  • IT:在 --profile app --profile web 栈上,mega2 配置的 bearer 可调通 website API,并可用测试 provider 记录或 mailpit 捕获证明发送路径。
  • plan-20260731.md DEP-06 标记为已由本计划交付;plan-long.md PT-09 更新;本计划全部非延后卡 Lifecycle=done 且 Acceptance=complete。

事实基线

本节为开工前快照(核对日 2026-08-02)。执行后状态以各任务卡 Lifecycle / Acceptance 与落地产物为准。行号仅作历史线索,开工须按 ER-02 刷新。

类别 当前事实 证据
代码入口(mega2) WebsiteMailClient POST {base}/api/internal/notifications/email,3s 超时、无 redirect、带 Idempotency-Key src/notification/website_mail.rs
代码入口(mega2) deliver_user_notification:先 in-app,再 best-effort 调 website;依赖 website_mail Option src/notification/service.rs
代码入口(mega2) 产品事件常量五类 src/notification/triggers.rs(EVENT_*)
代码入口(website) @libs/email:Resend/Cloudflare 已实现;smtp/sendgrid 未实现;模板仅 verification / resetPassword website libs/email/{email-sender,templates-sender,templates/templates}.ts
HTTP API(website) 无 app/api/internal/**;无产品通知路由 website apps/next-app/app/api/ 目录清单(核对日)
配置项(mega2) notification.website_mail_base_url + 恰好一个 bearer 源 src/config/ + website-mail.md §4
配置项(compose) MEGA_NOTIFICATION__WEBSITE_MAIL_* 与 MEGA2_INTERNAL_MAIL_BEARER 已对称注入;website-next 未注入 SMTP/mailpit docker-compose.test.yml mega2 / website-next
文档 契约已落盘;MN-05 note 写明 DEP-06 尚未在 website checkout 可用 docs/refactoring/website-mail.md §5 / MN-05 note
测试 mega2 有 wire mock;无真实 website API 栈级邮件断言 src/notification/service.rs tests
工作区前置 mega2 0.2.0;../orbit、../website sibling Cargo.toml:6;docker-compose.test.yml build context
外部参照 website mega2 @ 9afef8f36ec9d26d18bb968d89c8a7ebaebae2e1(CI pin;本地 tip 可能更新) .github/workflows/config-validation.yml checkout ref
外部参照 契约源 ADR-WA-08 / DEP-06 docs/plan/plan-20260731.md

执行后注记(2026-08-21 前端仓库改指): 本计划全部 ../website / genedna/website 锚点与 cd ../website && … 验证命令均为收口日快照,不作改写。 联调栈的前端自 2026-08-21 起改指 gitmono-dev/monoui 的 mega2 分支 (sibling ../monoui);WE-02..WE-06 的实现已逐文件移植到该仓,契约不变。 现行事实源见 ../refactoring/website-auth.md §头部 与 ../refactoring/website-mail.md 的 2026-08-21 增补。

当前缺口

ID 缺口 影响 证据 计划动作
GAP-01 website 无内部邮件 API mega2 产品邮件永远走 mock/失败;DEP-06 未关闭 website 无 api/internal WE-02..WE-04
GAP-02 无产品事件模板 即使有路由也无法按 event_type 渲染 @libs/email 仅 auth 模板 WE-03
GAP-03 smtp provider 未实现且 compose 未接线 mailpit website IT 无法用现有 mailpit 捕获 email-sender.ts smtp stub;compose 无 SMTP env WE-05、WE-06
GAP-04 无幂等存储 重放可能重复发信,违背契约 无 idempotency 实现 WE-04
GAP-05 文档仍写 DEP-06 未就绪 运维/后续计划误判 website-mail.md MN-05 note WE-07
GAP-06 CI website pin 可能滞后于本计划合入 tip 栈级 IT 仍跑旧镜像 config-validation.yml ref: 9afef8f… WE-06 / REL-WE-01

与其它计划的关系

计划/文档 关系 本计划处理
plan-20260731.md DEP-06 incoming;MN-05/06 已消费契约与客户端 本计划 交付 DEP-06;DOC 卡回写 DEP-06 状态
plan-long.md PT-09 邮件对齐收尾;PT-12 不在范围 WE-07 更新 PT-09 判断与日期索引
docs/refactoring/website-mail.md 契约事实源 实现必须对齐;契约变更走 WE-01/WE-07
docs/refactoring/test-infra.md mailpit = website IT WE-06 接线后同步
AGENTS.md 三门与 libra 流程(mega2 仓) 遵守;website 仓按其自身 AGENTS/pnpm 测试

评审结论与修订记录

维度 结论 修订动作
合理性 关闭 DEP-06 是 ADR-WA-08 闭环的必要步骤;客户端已就绪 无
可行性 website 已有 @libs/email 与 next-app 路由模式;缺口明确 无
任务卡粒度 按 API 表面 → 模板 → 幂等 → IT provider → compose/文档切开 成稿按 G-* 审计
依赖与顺序 WE-01→WE-02→…→WE-05→REL-WE-SITE→WE-06→WE-07→REL-WE-01 见实施顺序
完整性 含契约、安全、模板、幂等、IT、文档、pin/发布 无
安全性 内部 bearer、constant-time 比较、禁 cookie、禁日志泄漏 ADR-WE-01/02
功能正确性 五事件 allowlist + 422;幂等 duplicate ADR-WE-03/04
接口兼容 契约已在 mega2 发布;本计划不得破坏 JSON/路径 WE-01 冻结
数据流与控制流 受理后异步/同步发送均可,但响应前须完成幂等登记 ADR-WE-04
性能与容量 单请求有界;禁止无界模板扫描 GC-10 / ADR
可靠性与容错 provider 失败返回 5xx/upstream_unavailable;mega2 best-effort 风险表
可维护性 模板与路由落 website;契约仍在 mega2 website-mail.md WE-07

修订历史

日期 触发 变更内容 原卡 → 新卡 受影响的引用
2026-08-02 成稿 首版:Website 内部产品邮件 API(承接 DEP-06) N/A 全文
2026-08-02 review R1 Codex FAIL 修复:REL-WE-SITE、Verification、ADR-WE-04、D 组、deps + REL-WE-SITE;WE-02..05 → REL-WE-SITE child 实施顺序、REL、审计表、任务卡
2026-08-02 review R2 Codex FAIL:WE-04/05/07 Verification 非命令条目改为可执行命令 WE-04/05/07 Verification Review log
2026-08-02 review R3 Codex PASS;顺手消除 WE-06 Verification residual P2 WE-06 Verification Review log
2026-08-02 WE-01 契约/pin 基线审计落盘;Codex WE-01-R1 PASS website-mail.md 实现基线短节 WE-01、Review log
2026-08-02 WE-02 website 内部邮件路由+Bearer;Codex WE-02-R2 PASS website tip 4eac603 WE-02、Review log
2026-08-02 WE-03 五类产品模板+路由接线;Codex WE-03-R1 PASS website tip ac36736 WE-03、Review log
2026-08-02 WE-03 CTA 宿主修复确认;Codex WE-03-R4 PASS MEGA2_PUBLIC_BASE_URL / # WE-03、Review log
2026-08-02 WE-04 Idempotency-Key 状态机;Codex WE-04-R3 PASS website tip 4af4adf WE-04、Review log
2026-08-02 WE-05 test+smtp provider;Codex WE-05-R3 PASS(含 smtp CRLF 修复) website tip a52d703 WE-05、Review log
2026-08-02 REL-WE-SITE tip 发布;Codex REL-WE-SITE-R2 PASS tip a52d703 REL-WE-SITE、Review log
2026-08-02 WE-06 compose/CI pin/栈 IT;Codex WE-06-R1 PASS pin=a52d703;integration_website_mail WE-06、Review log
2026-08-02 WE-07 关闭 DEP-06 文档收口;Codex WE-07-R1 PASS website-mail / plan-long / plan-20260731 WE-07、Review log
2026-08-02 WE-07 关闭 DEP-06 文档收口;Codex WE-07-R2 PASS tip/plan-long/README/integration WE-07、Review log
2026-08-02 REL-WE-01 patch 0.2.1;Codex PASS;D1/D2 success fdf16ac/7afb301 REL-WE-01、Review log
2026-08-06 完成度复审(post-completion review) 登记收口后 CI website pin 前滚(a52d703…→2af89c8…,mega2 39f7433,website IT 切 Postgres):website-mail.md 实现基线/tip 对照补 2026-08-06 增补;WE-01/WE-06/WE-07 加执行后注记(pin 判据按收口日快照阅读);REL-WE-SITE 注记验收主机路径差异(/run/media/…→当前 checkout 根) N/A WE-01/WE-06/WE-07/REL-WE-SITE 注记、docs/refactoring/website-mail.md

已决议设计决策

ADR-WE-01: 契约冻结与仓库边界

  • Status: Accepted
  • Context: website-mail.md 已定义路径/鉴权/JSON;mega2 客户端已按该契约实现;website 尚无实现。
  • Decision: 以 docs/refactoring/website-mail.md 为跨仓契约唯一事实源。本计划默认 不改 路径、鉴权头、成功 202 形状与错误 code 表。若实现中发现不可行点,必须先改契约文档 + mega2 客户端同批修订,不得单侧漂移。实现代码主落点在 website 仓库;mega2 只改文档、compose/CI pin、可选栈级测试。
  • Alternatives considered: 在 mega2 再开代理 SMTP(拒绝——违反 ADR-WA-08);把契约迁到 website 仓(拒绝——客户端与 IT 已绑定 mega2 文档)。
  • Consequences: website PR 必须引用本计划卡号与 website-mail.md 章节。
  • Revisit when: 需要预渲染 HTML/附件或公开用户 API 时。

ADR-WE-02: 鉴权只认共享 Bearer

  • Status: Accepted
  • Context: 产品邮件是服务间调用;Better Auth cookie 属于浏览器会话。
  • Decision: 路由只接受 Authorization: Bearer <token>,与 MEGA2_INTERNAL_MAIL_BEARER(website)/ MEGA_NOTIFICATION__WEBSITE_MAIL_BEARER(mega2 IT)对称。使用 constant-time 比较;缺省/错误 → 401 unauthorized。禁止接受 session cookie、公开 API key 或无鉴权。生产 bearer 经 website 受控 env / secret 注入,不得写入示例仓库明文生产值。
  • Alternatives considered: mTLS(拒绝——本计划过重);复用 get-session cookie(拒绝——错误信任边界)。
  • Consequences: compose 已有 IT 明文 bearer(公开测试值);生产轮换窗口见 DEFER-WE-02。
  • Revisit when: 需要短期双 key 轮换时。

ADR-WE-03: 事件 allowlist 与模板归属

  • Status: Accepted
  • Context: mega2 触发器固定五类 event_type;website 现只有 auth 模板。
  • Decision: 本计划最小 allowlist 即为:cl.comment.created、cl.merged、issue.comment.created、issue.closed、item.referenced。每种事件一份 MJML→编译 HTML 产品模板(en + zh-CN),由 website 渲染;mega2 只传契约 payload 业务字段。未知事件 → 422 + unsupported_event。payload 缺必填字段 → 422 + invalid_payload。
  • Alternatives considered: 接受任意 event_type 并用通用模板(拒绝——不可审计);在 mega2 渲染 HTML(拒绝——已删本仓模板)。
  • Consequences: 新增事件类型需同步 website 模板与本 allowlist 文档。
  • Revisit when: PT-09 扩展更多通知类型时。

ADR-WE-04: 幂等与发送时机

  • Status: Accepted
  • Context: 契约(website-mail.md §2.1)要求同一 Idempotency-Key 重放返回同一受理结果且不得重复发送。
  • Decision: 采用三态记录 (bearer-scope, Idempotency-Key) → {delivery_id, fingerprint, state},state ∈ {in_progress, succeeded, failed}(存储:website-next IT 默认 SQLite 表或等价;单测可用内存 store):
    1. 首次: 原子插入 in_progress + 新 delivery_id + body fingerprint(唯一约束冲突则走重放分支);再调用 @libs/email 发送;成功则把 state 更新为 succeeded 并返回 202 {duplicate:false};发送失败则更新为 failed(或删除 in_progress 行)并返回 5xx,允许调用方用同一 key重试。
    2. 重放 succeeded + 同 fingerprint: 202 {duplicate:true},零次再次调用 provider。
    3. 重放 succeeded/in_progress + 不同 fingerprint: 409 idempotency_conflict。
    4. 重放 in_progress: 返回 409 或有界等待后读最终态(本卡选:立即 409 idempotency_conflict 对异请求;同 fingerprint 的并发第二者在唯一约束上失败后轮询/读到 succeeded 再回 duplicate——实现须保证 provider 至多调用一次)。
    5. 重放 failed: 视为可重试:转回 in_progress 再发送一次。
  • Alternatives considered: 仅成功后写入(拒绝——竞态可双发,违反契约);失败也标 succeeded(拒绝——永久吞掉重试)。
  • Consequences: 必须用唯一约束 + 状态机保证「同 key 至多一次成功发送」;多实例跨进程见 DEFER-WE-01。
  • Revisit when: 引入独立邮件 outbox worker 时。

ADR-WE-05: IT 投递后端

  • Status: Accepted
  • Context: 生产用 Resend/Cloudflare;smtp 未实现;compose 已有 mailpit。
  • Decision: 本计划交付 EMAIL_PROVIDER=test(或等价)记录型 provider:sendEmail 成功并写入可查询的内存/文件/表记录,供单元与栈级断言;另实现最小 smtp provider 指向 mailpit:1025,供可选捕获。compose website-next 默认 IT 使用 test provider(稳定、不依赖外网);需要捕获时再切 smtp + mailpit。禁止 IT 默认依赖真实 Resend/Cloudflare 云调用。
  • Alternatives considered: 只做 smtp(拒绝——CI 脆弱且当前 stub);只做 mock 路由不调用 @libs/email(拒绝——绕过真实发送栈)。
  • Consequences: WE-05 必须先于 WE-06 的栈级绿。
  • Revisit when: website 统一测试邮件策略变更时。

ADR-WE-06: 跨仓发布模型(website 家族 + mega2 家族)

  • Status: Accepted
  • Context: WE-02..WE-05 为 website 仓 implementation 卡,模板 G-07 禁止对 implementation 使用 no-release;同时又不能与 mega2 Cargo.toml 版本面混发。
  • Decision: 本计划使用两个发布家族:
    1. REL-WE-SITE:唯一发布点卡 REL-WE-SITE;成员 WE-02..WE-05(family child)。C 组为 website-native:focused pnpm 测试 → 在 website 仓提交并推送 mega2 分支 tip → 记录 tip SHA 到本计划 Review/验收证据。Version increment: N/A(不碰 mega2 版本面)。
    2. REL-WE-01:唯一发布点卡 REL-WE-01;成员改 mega2 的 WE-06(及必要 FIX);WE-01/WE-07 为 no-release 并继承其 C/D。C 组为 mega2 patch bump + AGENTS 三门 + push main。 顺序:先完成 REL-WE-SITE(website tip 可用),再 WE-06 pin 该 SHA,再 WE-07,最后 REL-WE-01。
  • Alternatives considered: 全部标 no-release(拒绝——违反 G-07);把实现细节挪出本计划只留 handoff(拒绝——削弱可执行任务卡)。
  • Consequences: 两仓推送由同一发布者串行执行(ER-12);禁止在 REL-WE-SITE 未完成时更新 mega2 CI pin。
  • Revisit when: website 仓引入独立日期计划模板时。

全局工程约束

全文适用 plan-template.md 的 GC-01..GC-12。补充:

  • GC-WE-01 跨仓提交: website 改动在 website 仓用该仓 VCS(libra/git 以 checkout 为准)提交;mega2 改动用本仓 libra(GC-12)。禁止把 website 源码复制进 mega2。
  • GC-WE-02 契约同步: 任何对 website-mail.md 的字段/状态码变更必须同批更新 mega2 WebsiteMailClient 与本计划 ADR。
  • GC-WE-03 脱敏: 日志与测试输出不得打印 bearer、完整收件人邮箱、完整 payload(与 website-mail.md §6 一致)。

执行检查必备需求(强制)

全文适用 plan-template.md 的 ER-01..ER-12。本计划补记:

本计划 ER-01 补记

  • compose 项目名统一 -p mega2-it。
  • website 卡额外要求 sibling ../website 在 mega2 分支且可读;缺失则相关卡 blocked。
  • website 本地验收以该仓 pnpm 单测 / 路由测试为准;不得用 mega2 cargo test --all 冒充 website 实现验收。
  • .env.test 仍按 AGENTS.md:缺失则停止询问。

本计划 ER-04 / 发布补记

  • 双家族发布(ADR-WE-06):
    • REL-WE-SITE:WE-02..WE-05 为 family child;唯一发布点 REL-WE-SITE(website tip 推送;Version increment: N/A)。
    • REL-WE-01:WE-06 为 family child;唯一发布点 REL-WE-01(mega2 patch bump + AGENTS 三门)。
  • WE-01 / WE-07 为 audit/docs:no-release,C/D 继承 REL-WE-01。
  • WE-02..WE-05 的 C/D coverage from: REL-WE-SITE;WE-06 的 C/D coverage from: REL-WE-01。

任务卡粒度规则

全文适用模板 G-01..G-11。本计划无 EX-* waiver。

实施顺序

  • WE-01 -> WE-02
  • WE-02 -> WE-03
  • WE-03 -> WE-04
  • WE-04 -> WE-05
  • WE-05 -> REL-WE-SITE
  • REL-WE-SITE -> WE-06
  • WE-06 -> WE-07
  • WE-07 -> REL-WE-01
  • DEP-01 -> WE-02(契约文档可读)
  • DEP-02 -> WE-06(mega2 compose/IT 基建)

依赖登记表

ID direction 类型 对象 Owner 产物与可用性判据 证据 超时与失败策略
DEP-01 incoming 跨计划 / 文档 docs/refactoring/website-mail.md(ADR-WA-08 契约) mega2 维护者 路径/JSON/错误码可引用;本计划 WE-01 核对无悬空 website-mail.md @ 2026-08-02 契约冲突先改 WE-01/文档再实现
DEP-02 incoming 跨计划 plan-20260731 MN-05/06 + ITW compose 已交付 WebsiteMailClient、compose bearer、website-next 可启 src/notification/website_mail.rs;docker-compose.test.yml 缺失则 WE-06 blocked,不得回退 SMTP
DEP-03 outgoing 跨计划 plan-20260731.md#DEP-06 本计划 WE-07 DEP-06 标注「由 plan-20260802 交付」+ tip SHA WE-07 文档 diff 接收方未改历史计划正文时,以本计划完成判据与 plan-long 为准
DEP-04 outgoing 长期能力 plan-long.md PT-09 WE-07 PT-09 写明内部邮件 API 已落地、剩余缺口 plan-long.md 冲突以本计划 ADR 为准并在 plan-long 注明

发布分组与并发窗口

ID 成员 唯一发布点 窗口规则 失败回滚顺序 理由
REL-WE-SITE WE-02, WE-03, WE-04, WE-05 REL-WE-SITE 子卡只在 website 仓本地提交、不推送;窗口期禁止更新 mega2 CI pin 按依赖逆序 revert website 本地提交 website implementation 必须有家族发布点(G-07/G-08;ADR-WE-06)
REL-WE-01 WE-06(及改 mega2 的 FIX) REL-WE-01 WE-01/WE-07 no-release 继承本点;窗口期禁止插入无关 mega2 切片 按依赖逆序 revert mega2 本地提交 mega2 文档/compose/pin 一次 patch 收口

并发声明: WE-02..WE-05 在 website 仓串行(I 写集相交)。WE-01 先于 WE-02。REL-WE-SITE 完成前不得开 WE-06 的 pin 更新。mega2 侧 WE-06→WE-07→REL-WE-01 串行。

发布者: 单一 Agent/人串行执行 REL-WE-SITE(website tip)与 REL-WE-01(mega2 patch),并跟踪各自 D 组(website 远端 CI 若有则记录;mega2 见 REL-WE-01 D 清单)。

发布窗口顺序: WE-01 → WE-02..WE-05 → REL-WE-SITE → WE-06 → WE-07 → REL-WE-01。

Phase 0: 契约核对与基线冻结

目标: 冻结 allowlist、pin、契约锚点,消除实现歧义。

进入条件: sibling ../website 可读;website-mail.md 存在。

退出条件: WE-01 Acceptance=complete(继承发布点后)。

Phase 1: Website API + 模板 + 幂等

目标: 可调用的内部 API:鉴权、五事件模板、幂等受理。

进入条件: WE-01 完成。

退出条件: WE-04 本地验收通过;随后 WE-05 → REL-WE-SITE 使 website tip 可被 pin。

Phase 2: IT provider 与栈接线

目标: test/smtp provider + compose 接线 + 栈级可证明调用。

进入条件: REL-WE-SITE 完成(website tip SHA 已知)。

退出条件: WE-06 本地栈级证据齐全。

Phase 3: 文档收口与 mega2 发布

目标: 关闭 DEP-06、更新 plan-long、patch 发布 mega2 文档/pin。

进入条件: WE-06 完成。

退出条件: REL-WE-01 Acceptance=complete。

字段全局默认与例外

  • Release boundary 默认: website 实现卡 family child of REL-WE-SITE;mega2 实现卡 family child of REL-WE-01
  • Task type 默认: implementation
  • Rollback mode 默认: revert(未推送);已推送 tip/main 后 immutable-release
  • Migration and rollback 默认: N/A(若 WE-04 在 website SQLite 增表,该卡写明 website 迁移步骤)
  • Security and privacy 默认: 继承 GC-07 + ADR-WE-02/脱敏
  • Performance budget 默认: 继承 GC-10;单请求同步发送,无扇出
  • Docs and compatibility impact 默认: 同步 website-mail.md 与相关 refactoring 文档

默认覆盖:

任务 偏离的字段 取值与理由
WE-01 Task type / Release boundary / C/D audit / no-release / REL-WE-01
WE-02..WE-05 Release boundary / Version increment / Release write set / C/D family child of REL-WE-SITE / N/A / N/A / REL-WE-SITE
WE-04 Migration and rollback 若新增 SQLite 表:website 侧 forward;IT db:push:sqlite
WE-06 Release boundary / C/D family child of REL-WE-01 / REL-WE-01
WE-07 Task type / Release boundary / C/D docs / no-release / REL-WE-01
REL-WE-SITE Task type / Version increment / Release write set release / N/A(website 无 mega2 版本面) / N/A
REL-WE-01 Task type / Version increment release / patch

规则 waiver: 无。

任务卡粒度审计表

任务 type axis recovery complete self-contained AC VER landing / prod-files scope deps writeset release split-from exception
WE-01 audit 契约/pin 冻结 revert 文档 yes yes 6/20 4/20 1/1 S DEP-01 no-overlap no-release N/A N/A
WE-02 implementation 内部路由+Bearer revert website yes yes 7/8 5/8 3/6 M WE-01,DEP-01 序列化于 WE-03 REL-WE-SITE child N/A N/A
WE-03 implementation 五事件产品模板 revert website yes yes 6/8 4/8 2/8 M WE-02 序列化于 WE-02 REL-WE-SITE child N/A N/A
WE-04 implementation 幂等受理 revert website yes yes 6/8 4/8 2/6 M WE-03 序列化于 WE-02 REL-WE-SITE child N/A N/A
WE-05 implementation test+smtp provider revert website yes yes 6/8 5/8 2/6 M WE-04 序列化于 WE-02 REL-WE-SITE child N/A N/A
REL-WE-SITE release website tip 发布 immutable-release yes yes 4/12 3/12 0/0 S WE-05 N/A REL-WE-SITE point N/A N/A
WE-06 implementation compose/pin/栈 IT revert mega2 yes yes 7/8 5/8 3/6 M REL-WE-SITE,DEP-02 序列化于 WE-07 REL-WE-01 child N/A N/A
WE-07 docs DEP-06/文档收口 revert docs yes yes 8/20 5/20 5/5 S WE-06 序列化于 WE-06 no-release N/A N/A
REL-WE-01 release mega2 patch 发布 immutable-release yes yes 5/12 4/12 0/3 S WE-07 N/A REL-WE-01 point N/A N/A

任务卡

Task WE-01: 契约与 website pin 基线审计

Task type: audit

Lifecycle / Acceptance: done / complete

执行后注记(2026-08-06 完成度复审): 冻结表「CI website pin」行(a52d703…)为核对日 2026-08-02 快照;plan-20260803 期间(mega2 39f7433,website IT 切 Postgres)pin 已 bump 为其直接子提交 2af89c874646005dd1a550053b5068f19bb7478a(仍含 WE-02..WE-05),当时同步了 website-auth.md / test-infra.md 但漏改 website-mail.md;复审已在 website-mail.md 实现基线节补 2026-08-06 增补说明。契约字段、事件 allowlist、bearer 键、分支与 REL-WE-SITE tip 等其余冻结项经复核仍与现行一致。

Description: 只读核对 website-mail.md 与 mega2 客户端字段、五事件常量、website tip/CI pin,产出本计划执行用的冻结清单(写入本卡 Deliverables 文档段落或 website-mail.md「实现基线」短节),不改生产行为。

Out of scope:

  • 不实现 API(由 WE-02 承接)
  • 不改 CI pin(由 WE-06 承接)

Current evidence:

事实 证据
契约已存在 docs/refactoring/website-mail.md:38-46
客户端已实现 src/notification/website_mail.rs:19-66
事件常量 src/notification/triggers.rs:13-17
website 无 internal 路由 ../website/apps/next-app/app/api/(无 internal/)
CI pin .github/workflows/config-validation.yml:91
smtp 未实现 ../website/libs/email/email-sender.ts:30-39

Acceptance criteria:

  • 冻结表列出:路径、鉴权头、JSON 字段、五 event_type、错误码、CI 当前 pin SHA
  • 标明 website 工作分支 mega2 与建议开发基线 tip(可与 CI pin 不同,须写明)
  • 确认 mega2 WebsiteMailClient 与契约无已知冲突;若有冲突登记为 P0 并阻断 WE-02
  • 确认 @libs/email smtp 未实现、仅 auth 模板——与 GAP-02/03 一致
  • Deliverables 文件落盘且内部链接可解析
  • 无 src/** / website 生产代码改动(allowlist = 文档)

Verification:

  • rg -n 'api/internal/notifications/email|Idempotency-Key|unsupported_event' docs/refactoring/website-mail.md
  • rg -n 'EVENT_CL_COMMENT_CREATED|EVENT_ITEM_REFERENCED' src/notification/triggers.rs
  • test ! -e ../website/apps/next-app/app/api/internal/notifications/email/route.ts(或等价「不存在」证据)
  • libra status --short --branch 仅允许文档 allowlist 变更

Dependencies: DEP-01

Deliverables: docs/refactoring/website-mail.md 新增「实现基线(plan-20260802)」短节,或 docs/plan/plan-20260802-baseline.md(二选一,本卡选 写入 website-mail.md 短节)

Implementation write set: docs/refactoring/website-mail.md(仅基线短节)

Release write set: N/A

Files likely touched: docs/refactoring/website-mail.md

Docs and compatibility impact: 仅开发文档

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: Inherited

Performance budget: N/A

Estimated scope: S

Version increment: N/A

Release boundary: no-release

C/D coverage from: REL-WE-01

Granularity: type=audit; axis=契约/pin冻结; recovery=revert文档后状态不变; complete=yes; self-contained=yes; AC=6/20; VER=4/20; landing=1; prod-files=1; scope=S; deps=DEP-01; writeset=no-overlap; release=no-release; split-from=N/A; exception=N/A


Task WE-02: 实现内部邮件路由与 Bearer 鉴权

Task type: implementation

Lifecycle / Acceptance: done / complete

Description: 在 website apps/next-app 新增 POST /api/internal/notifications/email:校验 Bearer(ADR-WE-02)、Idempotency-Key 存在性、JSON schema 基础字段;鉴权失败/非法请求按契约返回;本卡尚未接模板发送——对通过校验的已知/未知事件可先返回 422 unsupported_event(若 WE-03 未合入)或提供可注入的 handler hook;推荐本卡交付「校验 + 401/400 + 框架」,发送委托给后续卡通过同一模块扩展。

Out of scope:

  • 产品模板(WE-03)
  • 幂等存储(WE-04)
  • provider(WE-05)

Current evidence:

事实 证据
无 internal 路由 website app/api/
契约错误码表 website-mail.md §2.2
compose bearer 名 docker-compose.test.yml MEGA2_INTERNAL_MAIL_BEARER

Acceptance criteria:

  • 路由文件存在且仅处理 POST
  • 缺/错 Bearer → 401 + code=unauthorized;比较为 constant-time
  • 缺 Idempotency-Key 或非法 JSON → 400 + invalid_request
  • 不读取 Better Auth cookie 作为鉴权
  • 单元/路由测试覆盖 401/400;日志无 bearer
  • 环境变量键名与 compose 的 MEGA2_INTERNAL_MAIL_BEARER 一致
  • website README/AGENTS 有一行入口说明(可极短)

Verification:

  • cd ../website && pnpm exec vitest run --config vitest.api.config.ts tests/unit/email/internal-notifications-email-auth.test.ts(new 文件;覆盖缺 bearer→401、非法 JSON→400)
  • cd ../website && pnpm exec vitest run --config vitest.api.config.ts -t 'rejects missing bearer'(new)
  • rg -n 'MEGA2_INTERNAL_MAIL_BEARER|internal/notifications/email' ../website/apps/next-app/app/api/internal(锚点定位用,非判据)
  • rg -n 'timingSafeEqual|crypto\.timingSafeEqual' ../website/apps/next-app/app/api/internal ../website/libs -g '*.ts'(锚点定位用,非判据)
  • cd ../website && (libra status --short 2>/dev/null || git status --short) 无 /mega2/ 路径

Dependencies: WE-01, DEP-01

Deliverables: N/A(代码卡)

Implementation write set: ../website/apps/next-app/app/api/internal/**、../website/tests/unit/email/internal-notifications-email-auth.test.ts、可选 bearer helper、website 短文档

Release write set: N/A

Files likely touched: apps/next-app/app/api/internal/notifications/email/route.ts、tests

Docs and compatibility impact: website 短说明;契约不变

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: ADR-WE-02;fail-closed 无 bearer

Performance budget: 鉴权 O(1)

Estimated scope: M

Version increment: N/A

Release boundary: family child of REL-WE-SITE

C/D coverage from: REL-WE-SITE

Granularity: type=implementation; axis=内部路由+Bearer; recovery=revert website 提交; complete=yes; self-contained=yes; AC=7/8; VER=5/8; landing=3; prod-files=6; scope=M; deps=WE-01,DEP-01; writeset=序列化于WE-03; release=REL-WE-SITE child; split-from=N/A; exception=N/A


Task WE-03: 产品事件邮件模板(五类)

Task type: implementation

Lifecycle / Acceptance: done / complete

Description: 在 @libs/email 增加五类产品通知模板(ADR-WE-03),编译进 runtime HTML;路由对 allowlist 内事件选择模板并渲染(仍可不接真实 provider——可调用 sendEmail 但允许 WE-05 前用 test double)。未知事件保持 422 unsupported_event。

Out of scope:

  • 幂等(WE-04)
  • smtp/test provider 实现(WE-05)
  • 新事件类型(永久非目标于本卡;重启条件 = 新 trigger)

Current evidence:

事实 证据
仅 auth 模板 libs/email/templates/templates.ts
事件常量 src/notification/triggers.rs
payload 示例 website-mail.md §2.1

Acceptance criteria:

  • 五类 event_type 均有 en/zh-CN 模板与渲染函数
  • pnpm email:compile(或仓库等价)后 compiled.ts 含新产品模板
  • 路由对五类返回非 unsupported_event(在 test double 下可 202 或进入发送路径)
  • 未知 event_type → 422 unsupported_event
  • payload 缺关键字段 → 422 invalid_payload
  • 模板契约单测覆盖至少一类产品事件 + 未知事件

Verification:

  • cd ../website && pnpm email:compile
  • cd ../website && pnpm test -- tests/unit/email/template-contract.test.ts tests/unit/email/render-template.test.ts
  • cd ../website && pnpm exec vitest run -t 'product notification templates cover allowlisted event types'(new)
  • rg -n 'cl\.comment\.created|item\.referenced|unsupported_event' ../website/libs/email ../website/apps/next-app/app/api/internal(锚点定位用,非判据)

Dependencies: WE-02

Deliverables: N/A

Implementation write set: ../website/libs/email/**、internal route 渲染接线、tests

Release write set: N/A

Files likely touched: libs/email/templates/*、route.ts

Docs and compatibility impact: 可选更新 libs/email/README.md

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: payload 不入日志

Performance budget: 渲染为占位替换,无 MJML 运行时

Estimated scope: M

Version increment: N/A

Release boundary: family child of REL-WE-SITE

C/D coverage from: REL-WE-SITE

Granularity: type=implementation; axis=五事件产品模板; recovery=revert website 提交; complete=yes; self-contained=yes; AC=6/8; VER=4/8; landing=2; prod-files=8; scope=M; deps=WE-02; writeset=序列化于WE-02; release=REL-WE-SITE child; split-from=N/A; exception=N/A


Task WE-04: Idempotency-Key 受理去重

Task type: implementation

Lifecycle / Acceptance: done / complete

Description: 按 ADR-WE-04 实现幂等:成功发送后提交记录;重放 duplicate;冲突 409。

Out of scope:

  • 跨区域分布式 outbox(DEFER-WE-01)
  • provider 实现(WE-05)

Current evidence:

事实 证据
契约幂等要求 website-mail.md §2.1–2.2
mega2 每请求新 UUID key website_mail.rs(每次 Uuid::new_v4——重试去重依赖调用方稳定 key;本卡仍须实现服务端语义)

Acceptance criteria:

  • 缺 key → 400(与 WE-02 一致,回归不破)
  • 首次成功 → 202 {accepted:true, duplicate:false, delivery_id};发送前已占位 in_progress(ADR-WE-04)
  • 相同 key + 相同 fingerprint → 202 duplicate:true 且同一 delivery_id,provider 调用次数不增加
  • 相同 key + 不同 body fingerprint → 409 idempotency_conflict
  • 发送失败留下 failed(或清除占位)且可用同一 key重试成功一次
  • 自动化测试覆盖:首次成功、duplicate 零二次发送、conflict、失败可重试

Verification:

  • cd ../website && pnpm exec vitest run --config vitest.api.config.ts -t 'internal notifications email idempotency succeeds once'(new;断言 provider spy callCount===1)
  • cd ../website && pnpm exec vitest run --config vitest.api.config.ts -t 'internal notifications email idempotency duplicate does not resend'(new;断言 callCount 仍为 1)
  • cd ../website && pnpm exec vitest run --config vitest.api.config.ts -t 'internal notifications email idempotency conflict and failed retry'(new)
  • bash -c 'set -euo pipefail; if rg -n "create table.*idempot|idempotency_keys" -i ../website -g "*.ts" -g "*.sql"; then cd ../website && pnpm db:push:sqlite; else echo "OK: no SQLite idempotency schema in this slice"; fi'

Dependencies: WE-03

Deliverables: N/A

Implementation write set: website idempotency module、route、可选 schema、tests

Release write set: N/A

Files likely touched: apps/next-app/**、libs/**、drizzle schema(若有)

Docs and compatibility impact: 在 website-mail 实现基线短节注明存储介质

Rollback mode: revert(website);若已推送 IT DB 文件则 forward 清表

Migration and rollback: 若新增表:website IT SQLite db:push:sqlite;无 down 要求(IT 可重建卷)

Security and privacy: key 可记哈希;不得记 raw bearer

Performance budget: 每请求一次 KV/DB 点查

Estimated scope: M

Version increment: N/A

Release boundary: family child of REL-WE-SITE

C/D coverage from: REL-WE-SITE

Granularity: type=implementation; axis=幂等受理; recovery=revert website 提交; complete=yes; self-contained=yes; AC=6/8; VER=4/8; landing=2; prod-files=6; scope=M; deps=WE-03; writeset=序列化于WE-02; release=REL-WE-SITE child; split-from=N/A; exception=N/A


Task WE-05: test provider 与最小 SMTP provider

Task type: implementation

Lifecycle / Acceptance: done / complete

Description: 按 ADR-WE-05 实现 test(记录型)与最小 smtp(→mailpit)provider,并让内部路由默认 IT 走 test。

Out of scope:

  • compose 接线(WE-06)
  • Resend/Cloudflare 行为变更(永久非目标于本卡)

Current evidence:

事实 证据
smtp stub libs/email/email-sender.ts case 'smtp'
mailpit 端口 docker-compose.test.yml 11025/18025

Acceptance criteria:

  • EMAIL_PROVIDER=test(或项目既有配置键)时 sendEmail 成功并记录最后一封邮件(to/subject/html 可测)
  • EMAIL_PROVIDER=smtp 时能对可配置 host/port 发信(IT 指向 mailpit)
  • 内部路由在 test provider 下端到端单测:鉴权→模板→幂等→记录
  • 未配置生产云密钥时 IT 路径不尝试 Resend/Cloudflare
  • 单元测试覆盖 test provider;smtp 可用 mock 传输或标记 live/gated
  • libs/email AGENTS/README 更新 provider 说明一行以上

Verification:

  • cd ../website && pnpm test -- tests/unit/email/cloudflare.test.ts
  • cd ../website && pnpm exec vitest run -t 'test email provider records last message'(new)
  • cd ../website && pnpm exec vitest run -t 'smtp provider sends via configurable transport'(new;mock transport)
  • cd ../website && pnpm exec vitest run -t 'test provider path never calls resend or cloudflare http'(new;断言云 HTTP spy callCount===0)
  • bash -c 'set -euo pipefail; rg -n "case '\''smtp'\''" ../website/libs/email/email-sender.ts ../website/libs/email/templates-sender.ts | rg -v "not implemented|待实现|UnsupportedProvider|throw new Error" >/tmp/we05-smtp.hits; test -s /tmp/we05-smtp.hits'

Dependencies: WE-04

Deliverables: N/A

Implementation write set: ../website/libs/email/providers/**、email-sender.ts、config、tests、README

Release write set: N/A

Files likely touched: libs/email/**

Docs and compatibility impact: libs/email/README.md / AGENTS.md

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: smtp 凭据仅 env;IT 无密码或空密码按 mailpit 要求

Performance budget: N/A

Estimated scope: M

Version increment: N/A

Release boundary: family child of REL-WE-SITE

C/D coverage from: REL-WE-SITE

Granularity: type=implementation; axis=test+smtp provider; recovery=revert website 提交; complete=yes; self-contained=yes; AC=6/8; VER=5/8; landing=2; prod-files=6; scope=M; deps=WE-04; writeset=序列化于WE-02; release=REL-WE-SITE child; split-from=N/A; exception=N/A


Task REL-WE-SITE: website tip 家族发布点

Task type: release

Lifecycle / Acceptance: done / complete

执行后注记(2026-08-06 完成度复审): VER-3 中的仓库路径 /run/media/eli/data/gitmono/mega2 是验收当日执行主机的路径;当前 checkout 位于 /Volumes/Data/GitMono/mega2,该命令按「以当前 mega2 checkout 根目录为准」阅读(复审在当前路径重跑等价判定:版本面 Cargo.toml / bin/Cargo.toml 无本卡引入的改动;复审执行时工作区含完成度复审自身的未提交修改,与本卡无关,判据收窄为版本面而非全树 status 干净)。tip SHA 与其余判据不受影响。

Description: 聚合 WE-02..WE-05:跑 website focused 测试,提交并推送 mega2 分支 tip,记录 tip SHA(ADR-WE-06)。不 bump mega2 版本面。

Out of scope:

  • mega2 compose/pin(由 WE-06 承接)
  • mega2 patch(由 REL-WE-01 承接)

Current evidence:

事实 证据
CI 旧 pin .github/workflows/config-validation.yml:91
website 分支 mega2
tip SHA(本卡) a52d70362586ae5e171be5db2e5c5457d07ce366(含 WE-02..WE-05;含 smtp 多行响应修复)

Acceptance criteria:

  • WE-02..WE-05 均 locally-accepted,本切片无开放 P0/P1
  • focused website email 单测全绿:pnpm test -- tests/unit/email(34);pnpm test 全仓另有 pre-existing validators/credits 失败与本卡无关;pnpm test:api 需本机 :7001 服务(本环境未起,live API 套件跳过)
  • tip 已推送 website 远端 mega2;验收记录含完整 SHA a52d70362586ae5e171be5db2e5c5457d07ce366
  • 未修改 mega2 Cargo.toml / bin/Cargo.toml 的 version

Verification:

  • cd ../website && pnpm test -- tests/unit/email(focused;全仓 pnpm test 见 AC 说明)
  • cd ../website && (libra rev-parse HEAD 2>/dev/null || git rev-parse HEAD) → a52d70362586ae5e171be5db2e5c5457d07ce366
  • cd /run/media/eli/data/gitmono/mega2 && libra status --short 无本卡引入的版本面改动

Dependencies: WE-05

Deliverables: website tip SHA

Implementation write set: N/A

Release write set: N/A

Files likely touched: N/A(website 仓)

Docs and compatibility impact: 验收记录 SHA

Rollback mode: immutable-release

Migration and rollback: N/A

Security and privacy: N/A

Performance budget: N/A

Estimated scope: S

Version increment: N/A

Release boundary: family release point of REL-WE-SITE

C/D coverage from: self

Granularity: type=release; axis=website tip发布; recovery=前滚 website tip; complete=yes; self-contained=yes; AC=4/12; VER=3/12; landing=0; prod-files=0; scope=S; deps=WE-05; writeset=N/A; release=REL-WE-SITE point; split-from=N/A; exception=N/A


Task WE-06: compose / CI pin / 栈级邮件 IT

Task type: implementation

Lifecycle / Acceptance: done / complete

执行后注记(2026-08-06 完成度复审): ① VER「pin SHA 等于 REL-WE-SITE 验收 tip」在收口日成立;plan-20260803 期间 pin 已前滚为 tip 的直接子提交 2af89c8…(仍含 WE-02..WE-05),该判据按收口日快照阅读,现行 pin 以 workflow 文件与 website-auth.md 为准;② VER 中对 /tmp/rel-we-site.tip-sha 的比对是验收当日的临时产物比对,事后不可复现,等价证据为上一条的 pin/文档锚点链。

Description: 将 website tip 接入 mega2 IT:更新 website-next 环境(test provider、必要 SMTP)、刷新 CI website ref pin,并增加可运行的栈级证明(扩展既有 integration_website_auth 或新 bin/tests filter):带 bearer 调 website 内部 API 或经 mega2 触发后断言 test-provider/mailpit。

Out of scope:

  • 文档总收口(WE-07)
  • 版本 bump(REL-WE-01)

Current evidence:

事实 证据
compose 已有对称 bearer docker-compose.test.yml
CI pin 旧 9afef8f…
无栈级邮件断言 bin/tests/

Acceptance criteria:

  • website-next 注入 MEGA2_INTERNAL_MAIL_BEARER(已有)+ test provider 所需 env
  • CI checkout ref 更新为包含 WE-02..WE-05 的 website SHA,并同步 website-auth.md / website-mail.md pin 政策段落(若有)
  • 至少一条自动化栈级或黑盒用例证明 API 2xx(允许 env gate WEBSITE_IT=1)
  • mega2 仍不 depends_on: mailpit
  • test-infra.md 服务表注明 website-next 邮件 env
  • Dockerfile/IT 构建仍可通过(不破坏 SQLite drizzle COPY)
  • 失败路径:错误 bearer → 401 的 sanitized 证据(测试或脚本)

Verification:

  • docker compose -p mega2-it -f docker-compose.test.yml --profile app --profile web config | rg -n 'EMAIL_PROVIDER|MEGA2_INTERNAL_MAIL_BEARER|MEGA_NOTIFICATION__WEBSITE_MAIL'
  • source .env.test && WEBSITE_IT=1 cargo test -p mega2 --test integration_website_mail -- --test-threads=1(new:bin/tests/integration_website_mail.rs;未起栈时 SKIP;--no-run 已编译绿)
  • rg -n 'ref: ' .github/workflows/config-validation.yml 的 SHA 等于 REL-WE-SITE 验收 tip
  • bash -c 'set -euo pipefail; pin=$(rg -N -o "ref:[[:space:]]+([0-9a-f]{7,40})" -r "$1" .github/workflows/config-validation.yml | head -1); tip=$(cat /tmp/rel-we-site.tip-sha); test -n "$pin" && test -n "$tip" && test "$pin" = "$tip" && echo "OK: pin=$pin"'(/tmp/rel-we-site.tip-sha 由 REL-WE-SITE 验收写入)
  • source .env.test && WEBSITE_IT=1 cargo test -p mega2 --test integration_website_mail -- --test-threads=1 unauthorized_bearer(new filter;断言 HTTP 401;未起栈时 SKIP)

Dependencies: REL-WE-SITE, DEP-02

Deliverables: N/A

Implementation write set: docker-compose.test.yml、.github/workflows/config-validation.yml、bin/tests/integration_website_mail.rs、docs/refactoring/test-infra.md、pin 文档

Release write set: N/A(family child)

Files likely touched: compose、workflow、bin/tests、test-infra.md

Docs and compatibility impact: test-infra;pin 文档

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: IT bearer 仍为公开测试值

Performance budget: 栈级用例有界超时

Estimated scope: M

Version increment: N/A

Release boundary: family child of REL-WE-01

C/D coverage from: REL-WE-01

Granularity: type=implementation; axis=compose/pin/栈IT; recovery=revert mega2 提交; complete=yes; self-contained=yes; AC=7/8; VER=5/8; landing=3; prod-files=6; scope=M; deps=REL-WE-SITE,DEP-02; writeset=序列化于WE-07; release=REL-WE-01 child; split-from=N/A; exception=N/A


Task WE-07: 关闭 DEP-06 与文档收口

Task type: docs

Lifecycle / Acceptance: done / complete

执行后注记(2026-08-06 完成度复审): 本卡落盘的「tip 对照(WE-07)」表中「CI checkout 同 SHA」为收口日快照;pin 其后 bump 为 2af89c8…(见 WE-01 注记),website-mail.md 该表已于 2026-08-06 补注。其余收口项(DEP-06 关闭、plan-long PT-09、plan-20260731 DEP-06 行)经复核仍与现行一致。

Description: 更新 mega2 文档与计划索引:标记 DEP-06 已由本计划交付、刷新 website-mail.md MN-05 note、更新 plan-long.md PT-09、plan-20260731 DEP-06 行、docs/plan/README.md、notification.md/integration.md 必要段落。

Out of scope:

  • 代码行为变更(若发现必须改代码 → 开 FIX 卡,本卡不就地升级)
  • 版本发布(REL-WE-01)

Current evidence:

事实 证据
DEP-06 已由本计划交付 plan-20260731.md 依赖表 → plan-20260802
website tip / CI pin a52d703…;website-mail.md 实现基线 + tip 对照
栈级 IT integration_website_mail(WE-06)

Acceptance criteria:

  • website-mail.md 删除或改写「DEP-06 尚未可用」表述,改为已实现 + website tip SHA
  • plan-20260731.md DEP-06 状态更新为已交付并链接本计划
  • plan-long.md PT-09「当前判断」反映内部 API 已落地
  • docs/plan/README.md 登记 plan-20260802.md
  • notification.md / integration.md / test-infra.md 与实现一致
  • 无悬空 docs/*.md 链接
  • 列出 website 实现 tip SHA 与 mega2 文档提交对照表(短)
  • libra status 仅文档/计划 allowlist

Verification:

  • bash -c 'set -euo pipefail; if rg -n "DEP-06.*未就绪|website API is not yet available|尚未在 website checkout 可用" docs/refactoring/website-mail.md; then echo "FAIL: stale DEP-06 wording"; exit 1; else rc=$?; if [ "$rc" -ne 1 ]; then exit "$rc"; fi; echo "OK: no live DEP-06 gap wording"; fi'
  • rg -n 'plan-20260802' docs/plan/README.md docs/plan/plan-long.md
  • bash -c 'set -euo pipefail; test -f docs/refactoring/website-mail.md && test -f docs/plan/plan-20260802.md && rg -n "^## " docs/refactoring/website-mail.md docs/plan/plan-long.md >/dev/null'
  • bash -c 'set -euo pipefail; rg -n "plan-20260802|由 plan-20260802 交付|DEP-06" docs/plan/plan-20260731.md docs/plan/plan-long.md'
  • bash -c 'set -euo pipefail; libra status --short --branch | tee /tmp/we07.status; if rg -n "^(M|A|D|R|C|\\?\\?)\\s+(src/|bin/|Cargo|docker-compose|\\.github/)" /tmp/we07.status; then echo "FAIL: non-docs dirty"; exit 1; else echo "OK: no code/config dirty from WE-07"; fi'

Dependencies: WE-06

Deliverables: 上列 docs/plan 文件清单

Implementation write set: docs/refactoring/*.md、docs/plan/*.md

Release write set: N/A

Files likely touched: website-mail.md、plan-20260731.md、plan-long.md、README.md(plan)、notification.md、integration.md

Docs and compatibility impact: 本卡即文档

Rollback mode: revert

Migration and rollback: N/A

Security and privacy: N/A

Performance budget: N/A

Estimated scope: S

Version increment: N/A

Release boundary: no-release

C/D coverage from: REL-WE-01

Granularity: type=docs; axis=DEP-06/文档收口; recovery=revert docs; complete=yes; self-contained=yes; AC=8/20; VER=5/20; landing=5; prod-files=5; scope=S; deps=WE-06; writeset=序列化于WE-06; release=no-release; split-from=N/A; exception=N/A


Task REL-WE-01: mega2 patch 发布点

Task type: release

Lifecycle / Acceptance: done / complete

Description: 在 WE-07 本地验收与 review PASS 后,对 mega2 做 patch bump、三门、签名提交、推送;聚合 WE-06/WE-07 变更;跟踪适用 D 组。

Out of scope:

  • 新行为
  • website 仓版本发布(非本卡)

Current evidence:

事实 证据
当前版本 0.2.1(进入窗口权威 0.2.0 → patch+1)
发布提交 fdf16ac(version bump)+ 7afb301(plan close)
D1 Config Validation success · https://github.com/gitmono-dev/mega2/actions/runs/30753981056
D2 Git Protocol Smoke success · https://github.com/gitmono-dev/mega2/actions/runs/30753312370(WE-06 compose 命中 paths)
D3 claude-review N/A(直推 main)

Acceptance criteria:

  • 版本面两处 patch +1 且一致
  • AGENTS 三门 + cargo build + cargo build --tests 绿
  • 推送 main 成功
  • D 组按下列清单逐项关闭(命中则绿;不命中则书面 N/A)
  • 提交说明引用 plan-20260802 / DEP-06

D 组远端后置门(本卡登记):

项 workflow job 触发 paths 命中条件 判据
D1 .github/workflows/config-validation.yml validate-config push: branches:[main] 最终树改动命中该文件 paths:(现场读取;通常含 docker-compose.test.yml、bin/tests/**、docs/refactoring/test-infra.md、workflow 自身) gh run view <id> --json conclusion → success
D2 .github/workflows/git-protocol-smoke.yml git-protocol-smoke push: branches:[main] 仅当最终树命中该 workflow paths:;纯 docs/compose IT 通常不命中 → N/A 命中则 success;否则 N/A
D3 claude-review.yml — 非 push 直推 main 不适用 N/A

Verification:

  • rg -n '^version' Cargo.toml bin/Cargo.toml 两处相等且为进入窗口权威版本 patch+1
  • cargo +nightly fmt --all --check && cargo clippy --all-targets --all-features -- -D warnings && source .env.test && cargo test --all && cargo build && cargo build --tests
  • libra cat-file -p HEAD | rg -q '^gpgsig' 或按 ER-07/EX 记录(EX-01:sign-off-only;无 gpgsig)
  • gh run list --branch main --limit 5 + D1/D2 的 gh run view sanitized 结论

Dependencies: WE-07

Deliverables: 版本面 + 推送证据

Implementation write set: Cargo.toml、bin/Cargo.toml、Cargo.lock(仅版本)、本家族已暂存文档/compose

Release write set: Inherited

Files likely touched: Cargo.toml、bin/Cargo.toml、Cargo.lock

Docs and compatibility impact: 提交说明引用本计划

Rollback mode: immutable-release

Migration and rollback: N/A

Security and privacy: N/A

Performance budget: N/A

Estimated scope: S

Version increment: patch

Release boundary: family release point of REL-WE-01

C/D coverage from: self

Granularity: type=release; axis=mega2 patch发布; recovery=前滚新版本; complete=yes; self-contained=yes; AC=5/12; VER=4/12; landing=0; prod-files=3; scope=S; deps=WE-07; writeset=N/A; release=REL-WE-01 point; split-from=N/A; exception=N/A

测试矩阵

类别 必须覆盖 Target / command
单元(website) bearer、schema、模板、幂等、test provider cd ../website && pnpm test / pnpm test:api
集成(栈) compose web+app 调内部 API source .env.test && WEBSITE_IT=1 cargo test -p mega2 --test integration_website_mail -- --test-threads=1
配置 website_mail_* 仍 validate cargo run -p mega2 -- config validate
安全 无 bearer 401;日志无 secret website + mega2 redaction 既有
live/gated smtp→mailpit 可选 compose profile / env gate

追溯表

任务 来源/证据 mega2 落点 文档/兼容动作 指定测试
WE-01 website-mail.md docs only 基线短节 文档 rg
WE-02 ADR-WE-02 (website) 契约不变 website route tests
WE-03 triggers.rs EVENT_* (website) allowlist 文档 template tests
WE-04 website-mail.md 幂等 (website) 存储说明 idempotency tests
WE-05 ADR-WE-05 (website) email README provider tests
REL-WE-SITE ADR-WE-06 website tip tip SHA pnpm test && pnpm test:api
WE-06 DEP-02 compose compose/CI/integration_website_mail test-infra WEBSITE_IT=1
WE-07 DEP-03/04 docs/plan 关闭 DEP-06 链接门
REL-WE-01 ER-08 版本面 提交说明 三门 + D

里程碑验收与回滚

里程碑 完成条件 发布/证据 回滚或前滚
M0 WE-01 完成 基线短节 revert 文档
M1 WE-04 完成 website tip 可鉴权+模板+幂等 revert website
M2 WE-06 完成 栈级 IT 证据 revert compose/pin
M3 REL-WE-01 complete mega2 patch + D 组 前滚

故障恢复矩阵

故障点 可接受残留 恢复动作 禁止结果
website API 5xx in-app 已写、邮件未达 修 website;mega2 不回退 SMTP 恢复 [mail]
幂等 in_progress 崩溃 可留下 failed/可重试行 用同 key 重试至 succeeded;唯一约束防双发 同 key 成功发送两次
CI pin 错误 栈 IT 红 更新 pin 前滚 关掉 website IT

风险登记

风险 影响 缓解 任务
website SMTP 未实现导致 IT 空心 高:假绿 ADR-WE-05 test provider 强制 WE-05
mega2 每次新 Idempotency-Key 中:客户端重试不幂等 文档注明;服务端仍正确;DEFER 客户端稳定 key WE-04、DEFER-WE-03
跨仓顺序颠倒 高:pin 指向无 API tip 发布窗口顺序强制 WE-06、REL-WE-01
模板与 payload 漂移 中:422 增多 allowlist + invalid_payload 测试 WE-03
日志泄漏 PII/bearer 高 脱敏测试与 code review WE-02、WE-07

性能与容量摘要

操作 单次成本 累积成本 预算/上限 验证
内部邮件 API 1×DB/KV + 1×provider 每通知每收件人一次 同步 < 3s(对齐客户端超时) WE-06
模板渲染 O(占位替换) 每封一次 禁止运行时 MJML WE-03

兼容与文档收口

六个 N/A 类条目于 2026-09-15 据实回填勾选(WE-01..WE-07 各卡早已 done,收口 tip a52d703 / 0.2.1,仅本清单遗漏)。

  • docs/errors.md:N/A(website HTTP 错误码在 website-mail.md) —— 错误契约在 website-mail.md §2.2,本仓 docs/errors.md 无需新增
  • docs/refactoring/website-mail.md / notification.md / test-infra.md / integration.md 已同步
  • config/config.toml:N/A(无新 mega2 键;除非 WE-06 注释) —— website_mail_base_url / _bearer_ref / _bearer(config.toml:262-265)由 plan-20260731 MN-05/MN-06 引入,本计划未新增键,N/A 成立
  • 运行时 OpenAPI:N/A(内部 API 在 website,不进 mega2 OpenAPI)
  • README.md / AGENTS.md:N/A 或仅登记漂移 —— 本计划实现面全在 website 仓,两份根文档无需改动
  • callisto/migration:N/A —— 本仓无 schema 改动
  • 新 docs/*.md 引用真实存在 —— website-mail.md / notification.md / test-infra.md / integration.md 均存在;WE-02 Deliverables 的「docs/plan/plan-20260802-baseline.md」为二选一备选,已选写入 website-mail.md:277「实现基线(plan-20260802)」短节,故该路径不应存在,非悬空链接
  • plan-long.md / docs/plan/README.md 已同步

Review log

Round Scope Result P0/P1 P2 处置 Evidence
R1 成稿全文 FAIL P0 no-release/Verification;P1 审计/deps/D/ADR-04 已修 /tmp/mega2-plan-review/we-r1.txt
R2 R1 关闭项复审 FAIL P0:WE-04/05/07 Verification 非命令表述 已修 /tmp/mega2-plan-review/we-r2.txt
R3 Verification 复审 PASS 无 residual P2:WE-06「同上」已顺手改为精确命令 /tmp/mega2-plan-review/we-r3.txt
WE-01-R1 WE-01 实现交付 PASS 无 无 /tmp/mega2-we-review/we01-r1.txt
WE-02-R1 WE-02 交付 FAIL P1:bearer 长度不等 early-return 非全程 constant-time 已修:SHA-256+timingSafeEqual /tmp/mega2-we-review/we02-r1.txt
WE-02-R2 WE-02 复审 PASS 无 residual P2:只读沙箱无法复跑 vitest(环境限制) /tmp/mega2-we-review/we02-r2.txt
WE-03-R1 WE-03 实现交付 PASS 无 residual P2:只读沙箱未重跑 vitest(本地已绿) /tmp/mega2-we-review/we03-r1.txt
WE-03-R4 WE-03 CTA 复审 PASS 无 忽略同 tip 上 WE-04 写集 /tmp/mega2-we-review/we03-r4.txt
WE-04-R1 WE-04 交付 FAIL P0:in_progress 同 fingerprint 直接 409 已修 awaiting 轮询 /tmp/mega2-we-review/we04-r1.txt
WE-04-R2 WE-04 复审 FAIL P0:工作树被覆盖丢失实现 已重落地 /tmp/mega2-we-review/we04-r2.txt
WE-04-R3 WE-04 再审 PASS 无 无 /tmp/mega2-we-review/we04-r3.txt
WE-05-R1 WE-05 交付 FAIL 文档 AGENTS/README provider 过时;后续轮 smtp 半读 已修 /tmp/mega2-we-review/we05-r1.txt
WE-05-R2 WE-05 复审 FAIL AGENTS SMTP planned 残留 / 半包提前 resolve 已修 CRLF 终结 /tmp/mega2-we-review/we05-r2.txt
WE-05-R3 WE-05 再审 PASS 无 residual P2:只读沙箱未复跑 vitest /tmp/mega2-we-review/we05-r3.txt
REL-WE-SITE-R1 website tip 发布 FAIL P1:tip 漂移 8ef7… vs a52d703…;旧 tip 含 smtp P1 tip 对齐 a52d703 /tmp/mega2-we-review/rel-site-r1.txt
REL-WE-SITE-R2 tip 复审 PASS 无 residual P2:只读沙箱未复跑 vitest /tmp/mega2-we-review/rel-site-r2.txt
WE-06-R1 WE-06 交付 PASS 无 residual:未起栈跑 IT(--no-run+静态) /tmp/mega2-we-review/we06-r1.txt
WE-07-R1 WE-07 文档收口 PASS 无 无 /tmp/mega2-we-review/we07-r1.txt
REL-WE-01-R1 REL-WE-01 发布准备 PASS 无 residual:可并入 fmt 一行 /tmp/mega2-we-review/rel-we01-r1.txt
WE-07-R1 WE-07 交付 FAIL P1:integration.md Active targets 缺 integration_website_mail 已补表+示例命令 /tmp/mega2-we-review/we07-r1.txt

非目标与延后项

ID 延后内容 原因 重启条件 承接位置
DEFER-WE-01 website 持久邮件 outbox / worker 本计划只要受理幂等 丢信率或多实例重复不可接受 PT-09
DEFER-WE-02 生产 bearer 双 key 轮换窗口 非 MVP 首次生产轮换 website-mail.md 修订
DEFER-WE-03 mega2 客户端稳定 Idempotency-Key(重试同 key) 当前 best-effort 每次新 UUID 需要客户端重试去重 mega2 website_mail.rs
DEFER-WE-04 更多产品事件模板 超出现行五类 新 trigger 合入 PT-09 / 后续日期计划

完成判据

  • 所有任务卡满足 G-*;粒度审计表已填齐
  • 所有非延后任务 Lifecycle=done 且 Acceptance=complete
  • 所有 Verification 已运行并记录(website + mega2)
  • mega2 计划完成门:fmt / clippy / source .env.test && cargo test --all / build / build --tests(REL-WE-01)
  • website 侧 WE-02..WE-05 测试绿并记录 tip SHA
  • 文档与 DEP-06 关闭完成
  • 代码/计划 review 最终 PASS,P0/P1 关闭
  • REL-WE-01 推送与 D 组证据完成或 N/A
  • 修订历史完整
  • plan-long.md PT-09 已同步