feat: M3 — 伏笔账本 + 节奏引擎 + 大纲(含并发记账 bugfix)

- 伏笔账本:纯函数状态机(OPEN/PARTIAL/CLOSED/OVERDUE) + ForeshadowLedger repo;验收后到期扫描(BackgroundTask 自建 session 置 OVERDUE);登记/状态变更端点
- 节奏 + 三审齐:foreshadow-analyst + pace-checker 并入 LangGraph 并行审(REVIEW_SPECS),collect 分列落 chapter_reviews(conflicts/foreshadow_sug/pace),review SSE 加 foreshadow/pace 事件
- 大纲:outliner Agent 产 OutlineResult(含 foreshadow_windows),POST /outline 逐章 upsert outline 表;GET /foreshadow?status= 看板
- 前端:伏笔四泳道看板(OVERDUE 琥珀) + 大纲编辑器(窗口徽标) + 节奏节拍图(▁▃▅) + 审稿页消费 foreshadow/pace SSE
- bugfix(T3.8):并行三审共用请求 session 记账触发 'Session is already flushing' → foreshadow/pace 静默丢失;SqlAlchemyLedgerSink.record 改 add-only(靠端点/事务 commit),加并发回归测试
- M3 E2E:真实 DB + mock 网关零 token 走通 埋设→进展→验收后扫描 OVERDUE→看板 + 大纲含窗口 + 三审齐 SSE/留痕;E2E 暴露并钉住上述 bug
- 门禁绿:mypy 111 / pytest 228(0 xfailed) / alembic 无漂移;前端 gen:api/lint/tsc/vitest 69/build
This commit is contained in:
Yaojia Wang
2026-06-18 14:21:17 +02:00
parent 68f194a043
commit 5fb7bfb1de
74 changed files with 6529 additions and 126 deletions

View File

@@ -17,7 +17,7 @@ import uuid
from collections.abc import AsyncIterator
from typing import Annotated
from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, BackgroundTasks, Depends, Request
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from ww_core.domain.chapter_repo import ChapterRepo
@@ -59,6 +59,7 @@ from ww_api.services.accept_service import (
)
from ww_api.services.credentials import STUB_OWNER_ID
from ww_api.services.digest_extraction import extract_digest_facts
from ww_api.services.foreshadow_scan import SessionFactory, run_overdue_scan
from ww_api.services.project_deps import (
get_chapter_repo,
get_digest_append_repo,
@@ -67,6 +68,7 @@ from ww_api.services.project_deps import (
get_project_repo,
get_review_gateway,
get_review_repo,
get_session_factory,
get_writer_gateway,
)
@@ -302,14 +304,23 @@ async def accept_chapter(
chapter_no: int,
body: AcceptRequest,
request: Request,
background_tasks: BackgroundTasks,
chapter_repo: ChapterRepoDep,
digest_repo: DigestRepoDep,
review_repo: ReviewRepoDep,
gateway: DigestGatewayDep,
session: Annotated[AsyncSession, Depends(get_session)],
session_factory: Annotated[SessionFactory, Depends(get_session_factory)],
) -> AcceptResponse:
"""验收事务 + 冲突 gate§5.5gate事务前→ 终稿提炼 digest事务外R2
单原子事务(晋升 + digest + 裁决留痕)→ 一次 commit
单原子事务(晋升 + digest + 裁决留痕)→ 一次 commit → 登记**验收后伏笔到期扫描**
BackgroundTaskM3-b/d§6.2)。
到期扫描接 §5.5 步骤 3 的 `TODO(M3)` 占位:验收提交成功**后**,把
`current_ch > expected_close_to AND status≠CLOSED` 的伏笔确定性置 OVERDUE。
伏笔登记/回收PARTIAL/CLOSED的「采纳建议」经作者经 foreshadow 端点显式确认,
不在验收事务里 AI 静默改库(不变量 #3。**§7.4 持久性局限**BackgroundTask 进程内
跑、重启丢失——原型可接受(见 `services/foreshadow_scan.py`,不引入 jobs 表)。
"""
request_id = getattr(request.state, "request_id", None)
# R3审稿真相从领域表重读最近一条 chapter_reviews不依赖 checkpoint。
@@ -350,6 +361,17 @@ async def accept_chapter(
latest_review=latest_review,
decisions=body.decisions,
)
# 验收**提交成功后**登记伏笔到期扫描 BackgroundTaskM3-b/d。current_chapter=刚验收章号;
# 任务在请求 session 关闭后跑,故 run_overdue_scan 自建新 session 并 commit§7.4 局限可接受)。
background_tasks.add_task(
run_overdue_scan,
session_factory,
project_id=project_id,
chapter_no=chapter_no,
request_id=request_id,
)
return AcceptResponse(
project_id=project_id,
chapter_no=chapter_no,