diff --git a/docs/MASTER_PLAN_2026-08.md b/docs/MASTER_PLAN_2026-08.md index aebf8b0..fe1b7a8 100644 --- a/docs/MASTER_PLAN_2026-08.md +++ b/docs/MASTER_PLAN_2026-08.md @@ -14,28 +14,30 @@ ## 1. Q2 Roadmap 完成度盘点(TODO 审计) +> 状态列已更新到 2026-08-09 收尾时点。审计当时的判断保留在备注里。 + | 卡片 | 状态 | 备注 | |---|---|---| | A1 Sentry | ✅ 完成 | `891f209` | -| A2 OTel | ✅ 完成 | `8f00ad1`(依赖冲突留下 DX-1 后遗症 → PR-A 收尾) | -| A3 BullMQ | ⚠️ 半成品 | 模块在但 API 不兼容,Redis 模式下会崩进程 → **PR-C** | +| A2 OTel | ✅ 完成 | `8f00ad1`;依赖冲突后遗症由 #7 收尾 | +| A3 BullMQ | ✅ 完成 | #10 修 API 不兼容 + 进程兜底 | | A4 Redis cache/rate-limit | ✅ 完成 | `b15201e` | -| A5 /metrics | ✅ 完成 | `98a0d2d`(BullMQ 模式下队列指标归零 bug → PR-C) | +| A5 /metrics | ✅ 完成 | `98a0d2d`;BullMQ 模式下指标归零由 #10 修复 | | B1 邀请邮件自动化 | ✅ 完成 | `98a0d2d` | | B2 A/B winner UI | ✅ 完成 | `4619956` | -| B3 Conductor 进度 SSE | ⚠️ 半成品 | agent 流有 SSE,Conductor 计划页仍是 3s 轮询且错误静默 → PR-D 修错误可见性;SSE 化列入 §4 | +| B3 Conductor 进度 SSE | ✅ 完成 | 审计时为半成品 → #9 修错误可见性,#18 完成 SSE 化 | | B4 Hunter Email-Finder | ✅ 完成 | `4619956` | -| B5 EMAIL_EXISTS 文案 | ✅ 完成 | `98a0d2d`(但邀请弹窗 i18n 裸 key 回归 → PR-D) | -| C1 Playwright 冒烟 | ❌ 未启动 | → **PR-E** | -| C2 前端组件单测 | ⚠️ 起步 | 4 个组件测试文件;关键页面 0 覆盖 → PR-E 扩 | -| C3 后端集成测试 | ⚠️ 部分 | 377 单测很强,但 discovery→pipeline→send 端到端链路测试缺失(P0-1 能存活至今就是证据)→ **PR-A 补最小版**,PR-E 扩 | -| C4 CI 红线 | ❌ 且 CI 本身红 | `npm ci` 缺 `--legacy-peer-deps`,5 月起每次 push 都 fail → **PR-A** | -| C5 FormField a11y 重构 | ⚠️ 部分 | FormField 组件优秀,但 Ads / Translate / Calendar 等页未迁移(原生白底控件)→ PR-F | -| D1 Ads 真实下单 | ❌ 未启动 | 仍是离线 planner → §4 | -| D2 Marketplace 种子 | ❌ 未启动 | → §4 | -| D3 pgvector wiring | ✅ 完成 | `4619956` | +| B5 EMAIL_EXISTS 文案 | ✅ 完成 | `98a0d2d`;邀请弹窗裸 key 回归由 #9 修复 | +| C1 Playwright 冒烟 | ✅ 完成 | #12(5 条,已挂 CI) | +| C2 前端组件单测 | ✅ 完成 | 4 个 → 13 个文件 / 82 测试(#12/#13/#17/#18/#19) | +| C3 后端集成测试 | ✅ 完成 | #12 补齐 discovery→pipeline→contact→send 全链路 | +| C4 CI 红线 | ✅ 完成 | #7 转绿;#12 加 client vitest + Playwright job(共 5 个 job) | +| C5 FormField a11y 重构 | ✅ 完成 | #13(并顺带查出白底控件的根因是不存在的 CSS class) | +| D1 Ads 真实下单 | ⏸ 需人工解锁 | 要广告账号 + sandbox 凭据,涉真实计费 | +| D2 Marketplace 种子 | ✅ 完成 | #19(范围已调整,见 §4-3) | +| D3 pgvector wiring | ✅ 完成 | `4619956` 接线,但**从未真正工作**(embed 调用契约错) → #20 修复 | | D4 Plugin API spec | ✅ 完成 | `docs/PLUGIN_API_v0.md` | -| D5 Token metering | ⚠️ 部分 | 全局 token/cost 有;按 workspace×agent×月的账目视图缺 → §4 | +| D5 Token metering | ✅ 完成 | #17(覆盖面是部分的,见 §4-2) | 代码内 TODO/FIXME 注释:**0 个**(工程卫生很好,所有欠账都在文档里——现在也都在本计划里)。 @@ -107,9 +109,11 @@ SSRF 统一走 `safeFetch`(重定向后二次校验)、CSV 公式注入前 --- -## 2b. 执行状态(2026-08-09 更新) +## 2b. 执行状态(2026-08-09 收尾更新) + +**本计划全部批次 + Q2 roadmap 剩余可自动推进项均已合并。main 绿:server 656 测试 / client 82 测试 / Playwright 5 条 / CI 五个 job 全过。** -**全部止血批次已合并,main 绿:server 572 测试 / client 59 测试 / Playwright 5 条 / CI 五个 job 全过。** +测试基线变化:server 377 → 656(+279),client 0 → 82,E2E 0 → 5。CI 从 2026-05 起持续红,现已连续绿。 | 批次 | PR | 状态 | |---|---|---| @@ -123,6 +127,11 @@ SSRF 统一走 `safeFetch`(重定向后二次校验)、CSV 公式注入前 | PR-F2 前端一致性 | [#13](https://github.com/oratis/influencex/pull/13) | ✅ 已合并 | | PR-F1 服务端加固 | [#14](https://github.com/oratis/influencex/pull/14) | ✅ 已合并 | | 追加:限流/迁移竞态 | [#15](https://github.com/oratis/influencex/pull/15) | ✅ 已合并 | +| 文档同步 | [#16](https://github.com/oratis/influencex/pull/16) | ✅ 已合并 | +| D5 用量账目 | [#17](https://github.com/oratis/influencex/pull/17) | ✅ 已合并 | +| B3 Conductor SSE | [#18](https://github.com/oratis/influencex/pull/18) | ✅ 已合并 | +| D2 Creator Marketplace | [#19](https://github.com/oratis/influencex/pull/19) | ✅ 已合并 | +| 追加:embed 契约 + 缓存计费 | [#20](https://github.com/oratis/influencex/pull/20) | ✅ 已合并 | ### 执行过程中新发现的缺陷(原计划里没有) @@ -139,6 +148,9 @@ SSRF 统一走 `safeFetch`(重定向后二次校验)、CSV 公式注入前 | **无邮件服务商时 approve 的任务卡在 `stage='send'`** — dry-run 分支在同步 pipeline 前就返回 | P2 | PR-E 写测试时发现 | 未修,测试如实断言现状 | | **YouTube API 查询串未 encodeURIComponent** | P2 | PR-F1 实现时发现 | #15 已修 | | **`client/node_modules` 符号链接被误提交** — 指向作者机器绝对路径,check out 后砸掉真实安装(本地已触发 ELOOP) | P2 | 合并 #13 后自食其果 | #15 已修(.gitignore 去掉尾斜杠) | +| **D3「pgvector wiring」标记完成但从未工作** — `llm.embed({texts:[…]})` 把选项对象当输入传,又把返回对象当数组索引 → `findBestBrandVoice()` 永远返回 null。"没找到相似语调"是合法结果,所以失败完全不可见 | P1 | 写 D5 账本时发现 | #20 已修 | +| **缓存命中在计费** — `complete()` 对缓存命中跳过 `recordUsage()`(内存统计视为免费),却原样返回原次调用的 `usage` → 落库的 `agent_runs` 按全价记账,与 `getStats()` 自相矛盾 | P1 | 写 D5 账本时发现 | #20 已修(报 0 成本、保留 token 数、附 `cachedUsdCents` 可审计) | +| **`llm` 模块头声称支持流式,实为从未实现** — 三个 provider 都是单次 fetch + json()。这正是 B3 只能做粗粒度阶段而非 token 级进度的原因 | P3 | 做 B3 时发现 | #20 已更正注释 | ### 行为变更(需要知会用户) @@ -168,14 +180,25 @@ PR-F ── 任意时间,低风险尾部 ## 4. 止血之后:Q2 roadmap 续推 -1. **B3 Conductor SSE** — 🔄 进行中(wave 2) -2. **D5 usage 账目** — 🔄 进行中(wave 2)。技术前提已确认:`agent_runs` 表已有 workspace_id/agent_id/cost_usd_cents/tokens/started_at,**无需建表,纯聚合 + UI** -3. **D2 Creator Marketplace** — 🔄 进行中(wave 2)。**范围已调整**:roadmap 原文写"导入 100 个 KOL",但凭空生成 100 份"看起来像真人"的创作者档案等于把伪造记录当真实数据呈现给用户,不做。改为:机制照建,数据只来自本工作区真实抓取过的 KOL 公开字段(提升进目录,带来源标注),外加 ≤10 条**明确标注为示例**的数据供空实例演示 +1. **B3 Conductor SSE** — ✅ 已完成([#18](https://github.com/oratis/influencex/pull/18))。计划构建阶段是**粗粒度的真实检查点**而非模型内省:`server/llm` 无流式支持,token 级进度需先给三个 provider 加流式,属独立 PR +2. **D5 usage 账目** — ✅ 已完成([#17](https://github.com/oratis/influencex/pull/17))。无需建表,`agent_runs` 已有全部字段。**覆盖面是部分的**:`generateOutreachEmail`(最高频路径,签名不带 workspaceId)、brand-voice embedding、community 分类循环均未记账,失败的 run 也不记 token +3. **D2 Creator Marketplace** — ✅ 已完成([#19](https://github.com/oratis/influencex/pull/19))。**范围已调整并落实**:不生成 100 份假档案;数据只来自真实抓取的公开字段 + 6 条明确标注的样例。额外决定:样例行不可加入 campaign(`kols` 无 `is_sample` 列,标签无法随复制存活) 4. **D1 Ads 真实下单** — ⏸ **需人工解锁**:要 Meta/Google 广告账号与 sandbox 凭据,且涉及真实计费风险,不自行推进 5. **Hunter 扩展** — ⏸ **需人工决策**:Hunter Email-Finder 是付费 API,要先定预算 -6. **MULTITENANCY.md 收口** — ✅ 已完成([#11](https://github.com/oratis/influencex/pull/11),提前执行) +6. **MULTITENANCY.md 收口** — ✅ 已完成([#11](https://github.com/oratis/influencex/pull/11)) 7. **ContactModule/PipelinePage UI 合并评估** — 未启动 -8. **前端按权限隐藏控件** — 新增:#14 的 RBAC 收口只做了服务端,viewer 现在会看到点不动的按钮 +8. **前端按权限隐藏控件** — 未启动。#14 的 RBAC 收口只做了服务端,viewer 现在会看到点不动的按钮并收到 403 toast + +### 下一轮可直接开工的清单(按价值排序) + +1. **`generateOutreachEmail` 计入用量账本** —— 最高频的未记账 LLM 路径;需把 workspaceId 穿过 5 个调用点 +2. **前端权限门禁** —— 消除 viewer 的 403 toast 体验 +3. **无邮件服务商时 approve 卡在 `stage='send'`** —— 影响所有未配发信服务商的部署 +4. **SSE token 移出 query string** —— 需要一次性 stream ticket(TTL + 吊销),现有两个流端点都受影响 +5. **provider 流式** —— 解锁 B3 的 token 级进度,同时让 `llm` 模块头的承诺成真 +6. **Marketplace 下架/申诉流程** —— 目前撤下一条 listing 只能手工 DELETE +7. **DNS-rebinding SSRF** —— 需要 resolve-then-pin 派发器 +8. **既有明文 platform token 回填加密** —— 现为下次写入时才加密 ### 遗留的已知问题(已定位,未修) @@ -192,8 +215,9 @@ PR-F ── 任意时间,低风险尾部 - [x] PR-0~PR-D 全部合入 main,CI 绿 - [x] PR-E / PR-F 合入(#12 / #13 / #14) - [x] E2E_REVIEW 的 P0/P1 条目全部关闭(见 §2b) +- [x] §4 中可自动推进的 roadmap 项完成(B3 / D5 / D2,见上) +- [x] memory.md §6 已知 bug 表同步更新 - [ ] **生产部署一次**,冒烟:登录 / approve 发送 / ROI 页 / 邀请弹窗四点通过 ← **下一步,需人工执行 `./deploy.sh`** -- [ ] memory.md §6 已知 bug 表同步更新 ### 部署前必读(这批改动改变了启动前置条件) diff --git a/docs/memory.md b/docs/memory.md index 8112c83..f1c396c 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -25,7 +25,7 @@ | **Resend From email** | `contact@market.hakko.ai` | | **Resend Reply-To** | `market@hakko.ai` | | **Cloudflare** | DNS + CDN(不挡 Cloud Run 流量) | -| **Memory.md 上次更新** | 2026-04-25 (post `44324f9`) | +| **Memory.md 上次更新** | 2026-08-09 (post `#20`) | ### 1.1 当前 Admin @@ -257,19 +257,40 @@ kill %1 ## 6. 已知 bug(不阻塞但要记得) -| Bug | 影响 | 修复 ETA | -|---|---|---| -| `subscriptions` 表 dormant | 占空间 5MB | 永不修(删除会破坏 migration 历史) | -| Hunter API 仅对有外链网站的 KOL 有效 | 约 30% KOL 找不到邮箱 | Sprint 2 B4(Hunter Email-Finder 路径) | -| ContactModule 5s 轮询,多 tab 加倍 | rate-limit 撞 429 | 已加自适应退避(`44324f9` 之前的 `fa692ca`) | -| 无 frontend 测试 | 前端重构靠手动 | Sprint 1 C1, Sprint 2 C2 | -| 无 Sentry / OTEL | 线上 bug 等于天书 | Sprint 1 A1, A2 | -| In-process job queue | `--max-instances > 1` 会丢消息 | Sprint 1 A3 | -| pgvector 启用但 agent 没用 | 浪费索引存储 | Sprint 2 D3 | -| `ContactModule.jsx` 与 `PipelinePage.jsx` UI 重复 | 两个 page 显示相似数据 | 暂保留(迁移代价大于收益),Sprint 3 评估 | -| `process.env.K_SERVICE` 在 Cloud Run 自动设置但本地需手动 | 本地连不上 Unix socket | 本地用 SQLite fallback 即可 | +> **2026-08-09 全量更新** —— 一次全局 e2e review + 15 个 PR(#6–#20)关闭了下表大部分历史条目。完整对照见 [E2E_REVIEW_2026-08.md](./E2E_REVIEW_2026-08.md) 与 [MASTER_PLAN_2026-08.md](./MASTER_PLAN_2026-08.md)。 ---- +### 已关闭 + +| 原条目 | 关闭方式 | +|---|---| +| 无 frontend 测试 | 13 个文件 / 82 个 vitest + 5 条 Playwright,全部挂 CI(#12/#13) | +| 无 Sentry / OTEL | 早已接入;依赖冲突后遗症 #7 收尾 | +| In-process job queue 多副本丢消息 | BullMQ API 修复 + 发送原子抢占 + 进程级异常兜底(#10) | +| pgvector 启用但 agent 没用 | 实际是**接了但从未工作**(embed 调用契约错,`findBestBrandVoice` 永远返回 null)→ #20 修复 | +| ContactModule 5s 轮询撞 429 | 后台刷新不再置 loading + 请求序号守卫(#9);限流器桶隔离(#15) | +| Hunter API 仅对有外链网站的 KOL 有效 | 仍然成立,但已是产品决策(付费 API 预算)而非 bug | + +### 仍然成立 / 新增 + +| Bug | 影响 | 备注 | +|---|---|---| +| `subscriptions` 表 dormant | 占空间 | 永不修 | +| **无邮件服务商时 approve 卡在 `stage='send'`** | 未配 Resend/SMTP 的部署,审批过的任务永远到不了 monitor | dry-run 分支在同步 pipeline 前就 return;#12 的测试如实断言了现状 | +| **`generateOutreachEmail` 不计入用量账本** | 最高频 LLM 路径的花费不可见 | 签名不带 workspaceId,需穿过 5 个调用点 | +| **前端未按权限隐藏控件** | viewer 会看到点不动的按钮并收到 403 toast | #14 的 RBAC 只做了服务端 | +| **SSE token 走 query string** | 会进服务端/代理日志与浏览器历史 | 两个流端点都受影响,需一次性 stream ticket | +| **DNS-rebinding SSRF** | 公网域名解析到内网 IP 仍可通过 | `assertSafeUrl` 是字面主机检查,需 resolve-then-pin | +| **既有明文 platform token 未回填加密** | 历史行仍是明文 | 读时透明兼容、下次写入才加密 | +| `content_daily_stats` 全局 UNIQUE(content_url, stat_date) | 跨工作区同 URL 同日第二条快照被静默跳过 | fail-closed,彻底解决需改约束 | +| Marketplace 无下架/申诉流程 | 撤一条 listing 只能手工 DELETE | provenance 列可定位,需配合创作者 opt-out | +| **design.md 与现状脱节** | §10.3 说焦点还原未实现、§12 硬编码 FUNNEL_COLORS、§8.3 modal 契约现已是组件 | #13 之后未同步 | +| `ContactModule.jsx` 与 `PipelinePage.jsx` UI 重复 | 两个 page 显示相似数据 | 未评估 | + +### 这轮学到的、值得记住的失效模式 + +1. **静默错配比崩溃危险**:引用不存在的 CSS token(ErrorCard 白底白字)、不存在的 class(`className="input"` 原生控件)、错误的函数契约(`llm.embed` 永远返回 null)——三者都不报错,只是安静地渲染成错的样子或永远返回空。**"看起来像空状态"要当成 bug 线索查。** +2. **只在新库上测 = 测不到生产**:session 索引写进基础 schema,全新库没问题,已有库启动即死。563 个测试全绿也拦不住。**改 schema 必须在有数据的库上启动一次。** +3. **列可空 = 隐形数据丢失**:`workspace_id` 一直 nullable,任何忘记带它的 INSERT 都静默成功、然后从所有 scoped 读里消失。#11 用条件 NOT NULL 做了结构性根治。 ## 7. 与协作者的协议 @@ -342,4 +363,7 @@ type: `feat` / `fix` / `chore` / `docs` / `refactor`。scope: `discovery` / `out --- -**Last reviewed:** 2026-04-25 (post `44324f9`, prod revision `00049-w2x`) +**Last reviewed:** 2026-08-09 (post `#20`, main green: server 656 / client 82 / e2e 5). +Prod revision unchanged since `00049-w2x` — **this batch has not been deployed yet**; see +[MASTER_PLAN_2026-08.md](./MASTER_PLAN_2026-08.md) §5 for the pre-deploy checklist (the startup +contract changed: MAILBOX_ENCRYPTION_KEY now fail-fast, webhooks fail-closed without secrets).