路径说明(2026-09-20): 本文成稿时
docker-compose-storage-only.yml/docker-compose-storage-only.auth-none.yml/docker-compose.test.yml位于仓库根;2026-09-20 起三者迁入docker/目录(commit1854cae)。文中根目录原路径为历史记录,不再逐一回改;实际使用时请替换为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、无产品事件模板、无幂等受理。目标是:
- 在 website
mega2分支的apps/next-app实现与docs/refactoring/website-mail.md一致的内部邮件接口; - 用
@libs/email扩展产品通知模板,覆盖 mega2 现行五类产品事件; - 提供 IT 可验证的投递路径(测试 provider 和/或 SMTP→mailpit),使栈级 compose 不再只能依赖 mega2 侧 wire mock;
- 在 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.mdDEP-06 标记为已由本计划交付;plan-long.mdPT-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 |
- 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 时。
- 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 轮换时。
- 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 扩展更多通知类型时。
- 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):- 首次: 原子插入
in_progress+ 新delivery_id+ body fingerprint(唯一约束冲突则走重放分支);再调用@libs/email发送;成功则把 state 更新为succeeded并返回202{duplicate:false};发送失败则更新为failed(或删除 in_progress 行)并返回 5xx,允许调用方用同一 key重试。 - 重放
succeeded+ 同 fingerprint:202{duplicate:true},零次再次调用 provider。 - 重放
succeeded/in_progress+ 不同 fingerprint:409 idempotency_conflict。 - 重放
in_progress: 返回409或有界等待后读最终态(本卡选:立即409 idempotency_conflict对异请求;同 fingerprint 的并发第二者在唯一约束上失败后轮询/读到 succeeded 再回 duplicate——实现须保证 provider 至多调用一次)。 - 重放
failed: 视为可重试:转回in_progress再发送一次。
- 首次: 原子插入
- Alternatives considered: 仅成功后写入(拒绝——竞态可双发,违反契约);失败也标 succeeded(拒绝——永久吞掉重试)。
- Consequences: 必须用唯一约束 + 状态机保证「同 key 至多一次成功发送」;多实例跨进程见
DEFER-WE-01。 - Revisit when: 引入独立邮件 outbox worker 时。
- Status: Accepted
- Context: 生产用 Resend/Cloudflare;
smtp未实现;compose 已有 mailpit。 - Decision: 本计划交付
EMAIL_PROVIDER=test(或等价)记录型 provider:sendEmail成功并写入可查询的内存/文件/表记录,供单元与栈级断言;另实现最小smtpprovider 指向mailpit:1025,供可选捕获。composewebsite-next默认 IT 使用testprovider(稳定、不依赖外网);需要捕获时再切smtp+ mailpit。禁止 IT 默认依赖真实 Resend/Cloudflare 云调用。 - Alternatives considered: 只做 smtp(拒绝——CI 脆弱且当前 stub);只做 mock 路由不调用
@libs/email(拒绝——绕过真实发送栈)。 - Consequences: WE-05 必须先于 WE-06 的栈级绿。
- Revisit when: website 统一测试邮件策略变更时。
- Status: Accepted
- Context:
WE-02..WE-05为 website 仓implementation卡,模板G-07禁止对 implementation 使用no-release;同时又不能与 mega2Cargo.toml版本面混发。 - Decision: 本计划使用两个发布家族:
REL-WE-SITE:唯一发布点卡REL-WE-SITE;成员WE-02..WE-05(family child)。C 组为 website-native:focusedpnpm测试 → 在 website 仓提交并推送mega2分支 tip → 记录 tip SHA 到本计划 Review/验收证据。Version increment: N/A(不碰 mega2 版本面)。REL-WE-01:唯一发布点卡REL-WE-01;成员改 mega2 的WE-06(及必要 FIX);WE-01/WE-07为no-release并继承其 C/D。C 组为 mega2patchbump + AGENTS 三门 + pushmain。 顺序:先完成REL-WE-SITE(website tip 可用),再WE-06pin 该 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的字段/状态码变更必须同批更新 mega2WebsiteMailClient与本计划 ADR。 - GC-WE-03 脱敏: 日志与测试输出不得打印 bearer、完整收件人邮箱、完整 payload(与
website-mail.md§6 一致)。
全文适用 plan-template.md 的 ER-01..ER-12。本计划补记:
- compose 项目名统一
-p mega2-it。 - website 卡额外要求 sibling
../website在mega2分支且可读;缺失则相关卡blocked。 - website 本地验收以该仓
pnpm单测 / 路由测试为准;不得用 mega2cargo test --all冒充 website 实现验收。 .env.test仍按AGENTS.md:缺失则停止询问。
- 双家族发布(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(mega2patchbump + 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-02WE-02 -> WE-03WE-03 -> WE-04WE-04 -> WE-05WE-05 -> REL-WE-SITEREL-WE-SITE -> WE-06WE-06 -> WE-07WE-07 -> REL-WE-01DEP-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。
目标: 冻结 allowlist、pin、契约锚点,消除实现歧义。
进入条件: sibling ../website 可读;website-mail.md 存在。
退出条件: WE-01 Acceptance=complete(继承发布点后)。
目标: 可调用的内部 API:鉴权、五事件模板、幂等受理。
进入条件: WE-01 完成。
退出条件: WE-04 本地验收通过;随后 WE-05 → REL-WE-SITE 使 website tip 可被 pin。
目标: test/smtp provider + compose 接线 + 栈级可证明调用。
进入条件: REL-WE-SITE 完成(website tip SHA 已知)。
退出条件: WE-06 本地栈级证据齐全。
目标: 关闭 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 type: audit
Lifecycle / Acceptance: done / complete
执行后注记(2026-08-06 完成度复审): 冻结表「CI website pin」行(
a52d703…)为核对日 2026-08-02 快照;plan-20260803期间(mega239f7433,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/emailsmtp 未实现、仅 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 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 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 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 →
202duplicate: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 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/emailAGENTS/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 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;验收记录含完整 SHAa52d70362586ae5e171be5db2e5c5457d07ce366 - 未修改 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 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.mdpin 政策段落(若有) - 至少一条自动化栈级或黑盒用例证明 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 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-20260731DEP-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.mdDEP-06 状态更新为已交付并链接本计划 -
plan-long.mdPT-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 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 viewsanitized 结论
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,收口 tipa52d703/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-20260731MN-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已同步
| 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.mdPT-09 已同步