Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions docs/MASTER_PLAN_2026-08.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 个**(工程卫生很好,所有欠账都在文档里——现在也都在本计划里)。

Expand Down Expand Up @@ -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 | 状态 |
|---|---|---|
Expand All @@ -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) | ✅ 已合并 |

### 执行过程中新发现的缺陷(原计划里没有)

Expand All @@ -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 已更正注释 |

### 行为变更(需要知会用户)

Expand Down Expand Up @@ -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 回填加密** —— 现为下次写入时才加密

### 遗留的已知问题(已定位,未修)

Expand All @@ -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 表同步更新

### 部署前必读(这批改动改变了启动前置条件)

Expand Down
52 changes: 38 additions & 14 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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. 与协作者的协议

Expand Down Expand Up @@ -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).
Loading