docs: 登记 AC-2 契约 C3 扩(AI 对话端点)+ PROGRESS 台账
- memory/contracts.md:C3 扩(AC-2)AI 对话端点登记「稳定」——POST/GET
/projects/{id}/ai-messages 请求/响应 schema + per-kind meta 形状 +
冻结 schema 名;标注 @frontend AC-3 须 pnpm gen:api。
- PROGRESS.md:AC-2 标 ✅(commit c675a74)+ 一行台账 + 变更日志。
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
| 任务 | 状态 | 负责 | 依赖 | 备注 |
|
| 任务 | 状态 | 负责 | 依赖 | 备注 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| AC-1 `ai_messages` 表 + 迁移 | ✅ @db 2026-07-09 | @db | — | 计划 §2 canonical DDL;迁移 `ceabd3c2c00d`(down=`f6a7b8c9d0e1`);C2 扩已「稳定」,AC-2 可动工 |
|
| AC-1 `ai_messages` 表 + 迁移 | ✅ @db 2026-07-09 | @db | — | 计划 §2 canonical DDL;迁移 `ceabd3c2c00d`(down=`f6a7b8c9d0e1`);C2 扩已「稳定」,AC-2 可动工 |
|
||||||
| AC-2 repo + append/list 端点 + schema + 契约 | ⬜ | @backend | AC-1 | 计划 §3;批级 append 信封、newest-first 分页 |
|
| AC-2 repo + append/list 端点 + schema + 契约 | ✅ @backend 2026-07-09 | @backend | AC-1 | `c675a74`;`SqlAiMessageRepo`(批 append seq/thread_id + newest-first union/分页) + `POST/GET /projects/{id}/ai-messages` + schemas;C3 扩已「稳定」→ AC-3 可动工(须 `pnpm gen:api`) |
|
||||||
| AC-3 gen:api + AiConversationDrawer + useAiConversation + 接线 5 处交互 | ⬜ | @frontend | AC-2 | 计划 §4;本章对话 + 项目级生成两段 |
|
| AC-3 gen:api + AiConversationDrawer + useAiConversation + 接线 5 处交互 | ⬜ | @frontend | AC-2 | 计划 §4;本章对话 + 项目级生成两段 |
|
||||||
| AC-4 E2E + repo/端点/hook 测试 | ⬜ | @qa | AC-3 | 计划 §6 |
|
| AC-4 E2E + repo/端点/hook 测试 | ⬜ | @qa | AC-3 | 计划 §6 |
|
||||||
|
|
||||||
@@ -208,6 +208,7 @@ T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移(无漂移,users st
|
|||||||
|
|
||||||
> 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件`
|
> 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件`
|
||||||
|
|
||||||
|
- [2026-07-09] @backend — **✅ AC-2 repo + append/list 端点 + schema + 契约**(计划 §3,`c675a74`)。repo `packages/core/ww_core/domain/ai_message_repo.py`(`AiMessageRepo`/`SqlAiMessageRepo`/`AiMessageView`/`AiTurnRow`,经 `ww_core.domain` 导出):`append`(一批多条 bubble → 批内 `seq`=enumerate 0 基位置、同 `thread_id`、meta 新 dict 不原地 mutate、`add_all`+一次 flush+refresh,**只 flush 不 commit**) + `list_for_chapter`(`created_at DESC, seq DESC` newest-first;`chapter_no` 非 None → 本章 ∪ 项目级 `IS NULL`,None → 仅项目级;可选 `kind`;`limit/offset`)。schemas `apps/api/ww_api/schemas/ai_messages.py`:批级 append 信封 `AiMessageAppendRequest{thread_id,chapter_no(ge=1)|null,kind,tool_key,messages}` + `AiMessageInput{role,content(min1,max200_000),meta(序列化≤50_000)}` + `AiMessageView` + `AiMessageListResponse{messages}`;`Literal` 为 kind/role 枚举权威(边界 422)。端点 `apps/api/ww_api/routers/ai_messages.py`(prefix `/projects`,tag `ai-messages`,已注册 main):**`POST /projects/{id}/ai-messages`**(201,项目 404,校验 422,append 只 flush + 端点 `commit()`) + **`GET /projects/{id}/ai-messages?chapter_no=&kind=`+`PageDep`**(200,newest-first,只读)。**这是显式记录写入**——五个生成端点逐字节不变、只读(守 #3);`ai_messages` 旁路侧记录,`assemble()`/agent 永不读(守 #1/#6)。注入缝 `get_ai_message_repo`。**TDD**:repo 真 pg 单测 9 passed(seq 0..n-1 / 同 thread_id / `created_at DESC,seq DESC` / union 含 NULL / 分页 / flush-only 跨 session 不可见);端点集成 12 passed(round-trip / 本章∪项目级 / kind 过滤 / 404×2 / 422×5〔非法 role/kind、空 messages、空 content、超上限 content、超上限 meta〕 / commit 边界 / 响应 key 集合不泄漏)。**门禁全绿**:ruff/format 干净 · mypy **238 文件** · **`alembic check` 无漂移**(本任务不加模型)· `export_openapi` 成功(新端点+4 schema 已入表面)· pytest **992 passed**(971 基线 + 21)· 新模块 cov router 100%/schemas 100%/repo 97%(余 Protocol `...` 桩),全局 89%。契约 **C3 扩(AC-2)已登记「稳定」** → **@frontend AC-3 须 `cd apps/web && pnpm gen:api`** 后消费码生成名。
|
||||||
- [2026-07-09] @db — **✅ AC-1 `ai_messages` 表 + 迁移**(计划 §2 canonical DDL)。`packages/db/ww_db/models.py` 加 `AiMessage(UuidPk, CreatedAt, Base)`(补 `Uuid` 导入):`project_id`(FK projects CASCADE, NOT NULL, indexed)/`chapter_no`(Integer nullable=项目级)/`thread_id`(Uuid NOT NULL)/`seq`(Integer NOT NULL, repo 批内 0 基定序)/`kind`(Text 无 CHECK)/`tool_key`(Text nullable)/`role`(Text + `ck_ai_messages_role` CHECK author|ai)/`content`(Text NOT NULL 全文)/`meta`(JSONB NOT NULL server_default `'{}'`)/`id`+`created_at`(mixin);append-only 无 updated_at。复合索引 `ix_ai_messages_project_chapter=(project_id,chapter_no,created_at DESC)` 手写 `sa.text` DESC + FK 单列索引。迁移 `ceabd3c2c00d`(down=`f6a7b8c9d0e1`,**autogenerate 仅捕获 ai_messages 无杂散漂移**,DESC 表达式归一为 `sa.text`)。**T0.2 MVP schema 后第一张新业务表**——对话历史是 18 表都无的能力、正交于 review/job/手稿,属真新增(非重复建表);是**旁路侧记录非手稿真源**,`assemble()`/agent 永不读(守 #1/#3/#6,围栏留 AC-4/P4 收口)。**门禁全绿**:ruff/format 干净 · mypy **233 文件** · `alembic upgrade head` + **`alembic check` 无漂移** + **downgrade→upgrade 往复干净** · pytest **971 passed**(基线不变,无代码用新表)。契约 **C2 扩(AC-1)已登记「稳定」** → AC-2 @backend 可动工。
|
- [2026-07-09] @db — **✅ AC-1 `ai_messages` 表 + 迁移**(计划 §2 canonical DDL)。`packages/db/ww_db/models.py` 加 `AiMessage(UuidPk, CreatedAt, Base)`(补 `Uuid` 导入):`project_id`(FK projects CASCADE, NOT NULL, indexed)/`chapter_no`(Integer nullable=项目级)/`thread_id`(Uuid NOT NULL)/`seq`(Integer NOT NULL, repo 批内 0 基定序)/`kind`(Text 无 CHECK)/`tool_key`(Text nullable)/`role`(Text + `ck_ai_messages_role` CHECK author|ai)/`content`(Text NOT NULL 全文)/`meta`(JSONB NOT NULL server_default `'{}'`)/`id`+`created_at`(mixin);append-only 无 updated_at。复合索引 `ix_ai_messages_project_chapter=(project_id,chapter_no,created_at DESC)` 手写 `sa.text` DESC + FK 单列索引。迁移 `ceabd3c2c00d`(down=`f6a7b8c9d0e1`,**autogenerate 仅捕获 ai_messages 无杂散漂移**,DESC 表达式归一为 `sa.text`)。**T0.2 MVP schema 后第一张新业务表**——对话历史是 18 表都无的能力、正交于 review/job/手稿,属真新增(非重复建表);是**旁路侧记录非手稿真源**,`assemble()`/agent 永不读(守 #1/#3/#6,围栏留 AC-4/P4 收口)。**门禁全绿**:ruff/format 干净 · mypy **233 文件** · `alembic upgrade head` + **`alembic check` 无漂移** + **downgrade→upgrade 往复干净** · pytest **971 passed**(基线不变,无代码用新表)。契约 **C2 扩(AC-1)已登记「稳定」** → AC-2 @backend 可动工。
|
||||||
- [2026-07-08] @devops — **✅ QA-CR CR-D1 抬高后端依赖下界至 lockfile 主次版本(保可复现)**。8 个 pyproject(`apps/api` + `packages/{core,agents,config,db,llm_gateway,shared,skills}`)的后端运行时依赖下界从过旧值抬到实锁 MAJOR.MINOR:`langgraph>=1.2`/`langgraph-checkpoint-postgres>=3.1`/`openai>=2.43`/`anthropic>=0.111`/`google-genai>=2.9`/`instructor>=1.15`/`tenacity>=9.1`/`pydantic>=2.13`/`pydantic-settings>=2.14`/`structlog>=26.1`/`asyncpg>=0.31`/`alembic>=1.18`/`fastapi>=0.137`/`uvicorn>=0.49`/`cryptography>=49`(每条 ≤ lockfile 实锁,同小版本线的 sqlalchemy/psycopg 留原样)。`uv.lock` 变动为**纯元数据刷新**——仅各 workspace 成员 `[package.metadata].requires-dist` 的 specifier 字符串随下界更新,**零 pinned `version=` 变动、零 re-resolve、零第三方版本 bump**。root dev 工具下界(pytest/ruff/mypy/httpx)+ 前端 vitest 3.x/tailwind 4.x 升级评估**明确不纳入**(非运行时可复现关键,超范围)。**门禁**:`uv lock --check` 绿(元数据刷新后一致)· `uv run ruff check .` 干净 · pytest **971 passed**(与基线一致,无回归)。无源码/测试改动。
|
- [2026-07-08] @devops — **✅ QA-CR CR-D1 抬高后端依赖下界至 lockfile 主次版本(保可复现)**。8 个 pyproject(`apps/api` + `packages/{core,agents,config,db,llm_gateway,shared,skills}`)的后端运行时依赖下界从过旧值抬到实锁 MAJOR.MINOR:`langgraph>=1.2`/`langgraph-checkpoint-postgres>=3.1`/`openai>=2.43`/`anthropic>=0.111`/`google-genai>=2.9`/`instructor>=1.15`/`tenacity>=9.1`/`pydantic>=2.13`/`pydantic-settings>=2.14`/`structlog>=26.1`/`asyncpg>=0.31`/`alembic>=1.18`/`fastapi>=0.137`/`uvicorn>=0.49`/`cryptography>=49`(每条 ≤ lockfile 实锁,同小版本线的 sqlalchemy/psycopg 留原样)。`uv.lock` 变动为**纯元数据刷新**——仅各 workspace 成员 `[package.metadata].requires-dist` 的 specifier 字符串随下界更新,**零 pinned `version=` 变动、零 re-resolve、零第三方版本 bump**。root dev 工具下界(pytest/ruff/mypy/httpx)+ 前端 vitest 3.x/tailwind 4.x 升级评估**明确不纳入**(非运行时可复现关键,超范围)。**门禁**:`uv lock --check` 绿(元数据刷新后一致)· `uv run ruff check .` 干净 · pytest **971 passed**(与基线一致,无回归)。无源码/测试改动。
|
||||||
- [2026-07-08] @llm — **✅ QA-CR MEDIUM/LOW 网关+链整改批(CR-M2/M3/L1@llm/M1.4,TDD 逐项提交)**。**完成 6**:`32451a9` pricing 未知 model 发 `pricing_unknown_model` warning(成本仍 0);`9439b97` 三 adapter 瞬时错误消息经 `provider_error_summary(exc)` 只留类名+status(不泄 vendor 响应体);`832ab7d` `OpenAICompatAdapter.probe_connection()` 公开探测 + provider_deps 弃私有 `_client` 反手(CR-M1.4);`c9ffada` 续写链前一章无已验收终稿发 `chain_prior_accepted_missing` warning(CR-L1);`ec91c8f` 链 runner 改用公开 `graph.aget_state(config).interrupts` 判暂停(弃私有 `__interrupt__`);`6a739ca` review 节点五审 `asyncio.gather` 并行扇出(复用主图 add-only 记账并发范式 T3.8,collect 一次落库)。**SKIP 2**:CR-M2 `is_open()` CQS(惰性半开转移是唯一转半开点、拆分漏发命令会致熔断永不复位,风险>收益);CR-M3 `run_overdue_scan` 幂等(`scan_overdue` 本已是原子 `UPDATE...RETURNING`+`NOT IN[CLOSED,OVERDUE]`+commit-only-if-changed,且下一章 current+1 扫描自愈漏标,再动会触碰验收事务边界)。**门禁全绿**:后端 ruff/format 干净 · mypy **232 文件** · pytest **971 passed**(+11 新测:pricing 2 / 错误脱敏 4 / probe 2 / 续写缺前文告警 2 / 并行审 1)· 无 DDL/无迁移。守不变量 #1/#3/#4(熔断/验收事务/add-only 记账 sink 未动)。
|
- [2026-07-08] @llm — **✅ QA-CR MEDIUM/LOW 网关+链整改批(CR-M2/M3/L1@llm/M1.4,TDD 逐项提交)**。**完成 6**:`32451a9` pricing 未知 model 发 `pricing_unknown_model` warning(成本仍 0);`9439b97` 三 adapter 瞬时错误消息经 `provider_error_summary(exc)` 只留类名+status(不泄 vendor 响应体);`832ab7d` `OpenAICompatAdapter.probe_connection()` 公开探测 + provider_deps 弃私有 `_client` 反手(CR-M1.4);`c9ffada` 续写链前一章无已验收终稿发 `chain_prior_accepted_missing` warning(CR-L1);`ec91c8f` 链 runner 改用公开 `graph.aget_state(config).interrupts` 判暂停(弃私有 `__interrupt__`);`6a739ca` review 节点五审 `asyncio.gather` 并行扇出(复用主图 add-only 记账并发范式 T3.8,collect 一次落库)。**SKIP 2**:CR-M2 `is_open()` CQS(惰性半开转移是唯一转半开点、拆分漏发命令会致熔断永不复位,风险>收益);CR-M3 `run_overdue_scan` 幂等(`scan_overdue` 本已是原子 `UPDATE...RETURNING`+`NOT IN[CLOSED,OVERDUE]`+commit-only-if-changed,且下一章 current+1 扫描自愈漏标,再动会触碰验收事务边界)。**门禁全绿**:后端 ruff/format 干净 · mypy **232 文件** · pytest **971 passed**(+11 新测:pricing 2 / 错误脱敏 4 / probe 2 / 续写缺前文告警 2 / 并行审 1)· 无 DDL/无迁移。守不变量 #1/#3/#4(熔断/验收事务/add-only 记账 sink 未动)。
|
||||||
|
|||||||
@@ -239,6 +239,15 @@
|
|||||||
- **lifespan reaper**(M4-d):`main.py` `_lifespan` 在 `seed_stub_user` 之后 `SqlJobRepo(session).reap_zombies()`(幂等、自 commit)——进程重启丢 BackgroundTask 的残留 `running` 行标 `failed`,用户可见可重试。
|
- **lifespan reaper**(M4-d):`main.py` `_lifespan` 在 `seed_stub_user` 之后 `SqlJobRepo(session).reap_zombies()`(幂等、自 commit)——进程重启丢 BackgroundTask 的残留 `running` 行标 `failed`,用户可见可重试。
|
||||||
- **T4.3 行动**:`POST /style` → `job_repo.create(project_id,"style_learn")` + commit 返 202 `{job_id}`;`background_tasks.add_task(run_job, session_factory, job.id, work=<部分应用 run_style_extraction→StyleFingerprintWriteRepo.append 并返回 {version,dims_count} 摘要>)`。`work` 内自建 gateway(analyst)+ repo(用 `run_job` 传入的独立 session)。注入缝 `get_job_repo`/`get_session_factory` 测试经 `app.dependency_overrides` override。
|
- **T4.3 行动**:`POST /style` → `job_repo.create(project_id,"style_learn")` + commit 返 202 `{job_id}`;`background_tasks.add_task(run_job, session_factory, job.id, work=<部分应用 run_style_extraction→StyleFingerprintWriteRepo.append 并返回 {version,dims_count} 摘要>)`。`work` 内自建 gateway(analyst)+ repo(用 `run_job` 传入的独立 session)。注入缝 `get_job_repo`/`get_session_factory` 测试经 `app.dependency_overrides` override。
|
||||||
|
|
||||||
|
### C3 扩展(AC-2, 2026-07-09)· AI 对话(聊天记录)端点(独立 `routers/ai_messages.py`,已注册) owner @backend 状态: 稳定
|
||||||
|
- 来源:`docs/design/ai-chat-history-plan.md §3`。建于 **C2 扩(AC-1) `ai_messages` 表**之上。持久化 refine/rewrite/clarify/continue/generator 五类往复交换,供每章「AI 对话」抽屉重放。**这两端点是显式的记录写入**(前端在一轮交换结束后 POST 一批)——五个生成端点(refine/refine·clarify/rewrite SSE/rewrite·clarify/skills·generate)**逐字节不变、只读**(守不变量 #3)。`ai_messages` 是侧记录,assemble()/agent 永不读它(守 #1/#6)。snake_case;改字段 → **@frontend AC-3 须 `cd apps/web && pnpm gen:api`**。
|
||||||
|
- **`POST /projects/{project_id}/ai-messages`** ← `AiMessageAppendRequest{thread_id:uuid, chapter_no:int(ge=1)|null=null(NULL=项目级/工具箱), kind:Literal["refine","rewrite","clarify","continue","generator"], tool_key:str|null=null, messages:list[AiMessageInput](min1,max50)}`,其中 `AiMessageInput{role:Literal["author","ai"], content:str(min1,max200_000), meta:dict=(默认 {},序列化 ≤50_000 字符否则 422)}` → **201** `AiMessageListResponse{messages:list[AiMessageView]}`。批内 `seq`=0 基位置由 **repo** 赋值(客户端不传);同批共享 `thread_id`+`created_at`。项目不存在 → **404** `NOT_FOUND`;非法 kind/role、空 messages、content 超上限、meta 超上限 → **FastAPI 422**。append 只 flush,端点写后 `commit()`。
|
||||||
|
- **`GET /projects/{project_id}/ai-messages?chapter_no=&kind=` + `PageDep`(limit≤200,offset≥0)** → **200** `AiMessageListResponse`。**newest-first**(`created_at DESC, seq DESC`);`chapter_no` 非空 → 本章 ∪ 项目级(`chapter_no IS NULL`);缺省 → 仅项目级;可选 `kind` 过滤。每行 **完整 `content`**(抽屉 re-accept 直读)。项目不存在 → 404(同 `GET .../style`)。只读不 commit。
|
||||||
|
- **`AiMessageView`(append/list 回显,与 repo view 镜像)**:`{id:uuid, project_id:uuid, chapter_no:int|null, thread_id:uuid, seq:int, kind:str, tool_key:str|null, role:str, content:str, meta:dict, created_at:datetime}`。**冻结 schema 名(前端消费码生成名,勿手写)**:`AiMessageAppendRequest`/`AiMessageInput`/`AiMessageView`/`AiMessageListResponse`,信封键 **`messages`**(复数键,非 `items`)。
|
||||||
|
- **per-kind `meta` 形状(前端写、@frontend AC-3 据此,长文进 `content` 不进 `meta`)**:refine → author `{}`(content=原选段) / ai `{version_no,segment}`(content=重写段,segment 存**每条** ai 行供任意版 re-accept 重锚);rewrite → author `{version_no}`(content=折叠意见) / ai `{version_no}`(content=版本文本);clarify → author `{}`(content=答案) / ai `{parent_kind,options:[{label,value}],allow_free_text,verification}`(content=问题文本);continue → 仅 ai `{candidate_index}`(content=候选文本,`tool_key="continue"`);generator → author `{tool_key,input_fields}`(content=brief/字段摘要) / ai `{tool_key,output_kind}`(content=渲染预览文本)。
|
||||||
|
- 位置/缝:repo `packages/core/ww_core/domain/ai_message_repo.py`(`AiMessageRepo`/`SqlAiMessageRepo`/`AiMessageView`/`AiTurnRow`,经 `ww_core.domain` 导出;`append` 只 flush,`list_for_chapter` 只读);schemas `apps/api/ww_api/schemas/ai_messages.py`;router `apps/api/ww_api/routers/ai_messages.py`(prefix `/projects`,tag `ai-messages`);注入缝 `get_ai_message_repo`(`services/project_deps.py`),测试经 `app.dependency_overrides` 注内存 fake。DoD 全绿:ruff/format/mypy(238 files)/alembic check(无漂移,本任务不加模型)/pytest(**992 passed**);新模块 cov:router 100% / schemas 100% / repo 97%(余 Protocol `...` 桩)。
|
||||||
|
- **@frontend 行动(AC-3)**:`cd apps/web && pnpm gen:api` 纳入 `POST/GET /projects/{id}/ai-messages` + 上述 4 个 schema。
|
||||||
|
|
||||||
## C4 · 编排器接口(LangGraph 写章图) owner @llm 状态: 稳定(M1/T1.3, 2026-06-18;M2 扩四审/验收)
|
## C4 · 编排器接口(LangGraph 写章图) owner @llm 状态: 稳定(M1/T1.3, 2026-06-18;M2 扩四审/验收)
|
||||||
- 来源:`ARCHITECTURE.md §5.2`(图)/ §7.3(SSE)。**M1 仅单 `write` 节点**;并行四审/collect/interrupt(accept) 属 M2。
|
- 来源:`ARCHITECTURE.md §5.2`(图)/ §7.3(SSE)。**M1 仅单 `write` 节点**;并行四审/collect/interrupt(accept) 属 M2。
|
||||||
- 位置:`packages/core/ww_core/orchestrator/`。
|
- 位置:`packages/core/ww_core/orchestrator/`。
|
||||||
|
|||||||
Reference in New Issue
Block a user