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:
@@ -16,13 +16,15 @@ from openai import AsyncOpenAI
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from ww_config import get_settings
|
||||
from ww_core.domain import ForeshadowLedgerRepo, SqlForeshadowLedgerRepo
|
||||
from ww_core.domain.chapter_repo import ChapterRepo, SqlChapterRepo
|
||||
from ww_core.domain.digest_repo import DigestAppendRepo, SqlDigestAppendRepo
|
||||
from ww_core.domain.outline_write_repo import OutlineWriteRepo, SqlOutlineWriteRepo
|
||||
from ww_core.domain.project_repo import ProjectRepo, SqlProjectRepo
|
||||
from ww_core.domain.repositories import MemoryRepos
|
||||
from ww_core.domain.review_repo import ReviewRepo, SqlReviewRepo
|
||||
from ww_core.memory.sql_repositories import sql_memory_repos
|
||||
from ww_db import get_session
|
||||
from ww_db import get_session, get_sessionmaker
|
||||
from ww_db.models import User
|
||||
from ww_llm_gateway import (
|
||||
Gateway,
|
||||
@@ -42,6 +44,7 @@ from ww_api.services.credentials import (
|
||||
CredentialStore,
|
||||
SqlCredentialStore,
|
||||
)
|
||||
from ww_api.services.foreshadow_scan import SessionFactory
|
||||
from ww_api.services.provider_deps import _PROVIDER_BASE_URLS
|
||||
|
||||
# 单用户 stub 的占位邮箱(多租户化时由真实主体替换)。
|
||||
@@ -95,6 +98,30 @@ def get_digest_append_repo(
|
||||
return SqlDigestAppendRepo(session)
|
||||
|
||||
|
||||
def get_foreshadow_repo(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> ForeshadowLedgerRepo:
|
||||
"""伏笔账本写侧 repo(登记/状态变更端点;只 flush,端点提交)。测试经 override 注 fake。"""
|
||||
return SqlForeshadowLedgerRepo(session)
|
||||
|
||||
|
||||
def get_outline_write_repo(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> OutlineWriteRepo:
|
||||
"""大纲写侧 repo(大纲生成端点逐章 upsert;只 flush,端点提交)。测试经 override 注 fake。"""
|
||||
return SqlOutlineWriteRepo(session)
|
||||
|
||||
|
||||
def get_session_factory() -> SessionFactory:
|
||||
"""验收后到期扫描的**独立 session 工厂**缝。
|
||||
|
||||
BackgroundTask 在请求 session 关闭后才跑,必须自建 session(不复用 `get_session`)。
|
||||
返回的工厂 `()` → `async with` 得一个新 `AsyncSession`。测试经 `app.dependency_overrides`
|
||||
注入 fake 工厂(避免真起后台线程/真连 DB)。
|
||||
"""
|
||||
return get_sessionmaker()
|
||||
|
||||
|
||||
async def build_gateway_for_tier(
|
||||
session: AsyncSession, store: CredentialStore, tier: Tier
|
||||
) -> Gateway:
|
||||
@@ -160,3 +187,11 @@ async def get_digest_gateway(
|
||||
"""验收终稿 digest 提炼(light 档位)的可注入网关缝。测试经 override 注 mock。"""
|
||||
store = SqlCredentialStore(session)
|
||||
return await build_gateway_for_tier(session, store, "light")
|
||||
|
||||
|
||||
async def get_outline_gateway(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> Gateway:
|
||||
"""大纲生成(analyst 档位)的可注入网关缝。测试经 override 注 mock(产 OutlineResult)。"""
|
||||
store = SqlCredentialStore(session)
|
||||
return await build_gateway_for_tier(session, store, "analyst")
|
||||
|
||||
Reference in New Issue
Block a user