# AI 合同平台 V2 Phase 3 UI 缓存字段 cleanup prestate 固化 ## 对齐范围 本记录对齐《AI 合同平台 V2 下一阶段开发优化方案》的 Phase 3 schema 硬化方向,承接 Slice 3.4 的 UI 展示字段派生和旧 UI 缓存治理。 当前结论不是进入删列 migration,而是先把“旧 UI 缓存字段仍保留但已一致”固化为 cleanup migration 前状态。 ## 当前 smoke 基线 - 权威 V2 smoke PostgreSQL:`192.168.3.90` 上的 `yuqei_contract` - Contract API:`http://192.168.3.90:18100` - Alembic actual/head:`20260630_0021` - `display_cache_consistency.status`:`ok` - `display_cache_consistency.checked_rows`:`12010` - `display_cache_consistency.mismatch_count`:`0` - 回滚 snapshot:`/opt/apps/ai-contract-platform-v2-smoke/backups/display-cache/display-cache-backfill-candidate-2026-06-30T17-37-24.json` - 本地备份:`project-docs/qa-artifacts/display-cache-governance/2026-06-30T17-37-24/display-cache-backfill-candidate-2026-06-30T17-37-24.json` 这表示 smoke 数据已经从“旧 UI 缓存存在 8000 条不一致”推进到“旧列仍保留、旧值与派生值一致”的可控状态。 ## 本次工程化固化 新增 runtime schema-check 检查项: - `display_cache_cleanup_prestate` 检查目标: 1. `contracts.status_label` 2. `contracts.status_tone` 3. `contracts.next_action` 4. `contracts.next_actions` 检查语义: - 四个旧列仍存在,且 display cache mismatch 为 0:`ok` - 四个旧列仍存在,但 mismatch 不为 0:`warning` - `contracts` 表缺失:`warning`,由 `core_tables` 继续报 `error` - 任一旧列在正式 cleanup migration 前缺失:`error` 新增此检查的原因是:cleanup migration 前,旧列仍是兼容/回滚面;如果有人提前删列,应明确报错,而不是让 display cache consistency 变成模糊的 SQL/连接异常。 ## Cleanup migration 进入条件 只有同时满足以下条件,才建议设计删列 migration: - API 响应中的 `status_label/status_tone/next_action/next_actions` 已完全由 mapper/serializer 派生。 - 后端业务写路径不再直接依赖四个旧列。 - 前端不再提交旧 UI 缓存字段作为业务输入。 - 搜索索引重建可以从事实字段和派生规则生成展示文案。 - smoke 环境连续多轮 `display_cache_consistency` 与 `display_cache_cleanup_prestate` 均为 `ok`。 - 保留并验证可回滚 snapshot。 ## 建议拆分顺序 1. Slice 3.5.1:确认 `next_action` 人工编辑入口是否保留。 2. Slice 3.5.2:先停止手写 `status_label/status_tone`,但保留列和 API 字段。 3. Slice 3.5.3:拆分 `next_action/next_actions` 语义,必要时迁移到 `action_hint` 或 action override 结构。 4. Slice 3.5.4:再进入 cleanup migration 设计评审。 ## 本次验证 ```powershell python -m pytest yuqei-contract-platform\services\contract-api\tests\test_health.py yuqei-contract-platform\services\contract-api\tests\test_display_cache_governance.py -q ``` 结果:13 个测试通过。 覆盖场景: - 本地 auto-created schema 中旧列保留且一致,`display_cache_cleanup_prestate=ok`。 - 缺少核心表时,prestate 检查只 warning,核心表检查继续负责 error。 - 存在 stale display cache 时,prestate 检查 warning。 - 提前删除旧 UI 缓存列时,prestate 检查 error,并明确指出缺失列。 ## 下一步 下一步仍按 Phase 3 小刀推进:先做 `next_action` 人工编辑入口的产品语义决策,然后进入停写 `status_label/status_tone` 的实现。不要直接删除旧列。 ## Slice 3.5.1 / 3.5.2 实施记录 本次继续沿 Phase 3 cleanup 路径收口,没有进入删列 migration。 ### next_action 语义确认 审计结果: - 前端合同编辑面板 `apps/contract-web/src/app/contracts/[id]/contract-edit-panel.tsx` 仍展示并提交 `next_action` 输入。 - Contract API 的 `ContractUpdateRequest` 仍允许更新 `next_action` 与 `next_actions`。 - 文件上传、审查任务、条款修复、审批实例和履约任务仍会影响 `next_action/next_actions` 的上下文派生。 结论: - `next_action/next_actions` 不能在本片作为纯 UI 缓存清理。 - 后续若要删除旧列,必须先决定人工提示是否迁移为 `action_hint` 或 action override 结构。 ### status_label/status_tone 停写第一片 本次新增统一 helper: - `sync_contract_status_display_cache(row)` 语义: - 只根据 `row.status` 派生并写回旧兼容列 `status_label/status_tone`。 - 不处理 `next_action/next_actions`。 - 旧列仍保留,用于兼容、回滚和 schema-check。 已覆盖的生产 SQLAlchemy 写路径: - 合同创建:`ContractCoreRepository.add_contract_in_session` - 合同状态流:`ContractAuditRepository.transition_contract` / `transition_contract_in_session` - 审批提交、重提、通过、退回:经 `SqlAlchemyTransitionOrchestration` 统一进入派生写入 - 归档:经同一 transition 路径派生写入 - 发起审查:设置 `status=reviewing` 后统一派生写入 本片没有做: - 没有删除数据库列。 - 没有修改 OpenAPI/前端响应字段。 - 没有移除 `next_action` 前端编辑入口。 - 没有重构 in-memory demo 仓储,因为本片目标是生产 SQLAlchemy 持久化路径停写。 ### 本次验证 ```powershell python -m compileall -q yuqei-contract-platform\services\contract-api\src\yuqei_contract_api python -m pytest yuqei-contract-platform\services\contract-api\tests\test_contract_workflow.py yuqei-contract-platform\services\contract-api\tests\test_display_cache_governance.py yuqei-contract-platform\services\contract-api\tests\test_health.py -q ``` 结果:通过。 新增回归: - `test_contract_status_display_cache_is_derived_on_write_paths` 验证点: - 真实审批流执行后,直接读取数据库行。 - `contracts.status_label` 等于 `status_label_for_status(row.status)`。 - `contracts.status_tone` 等于 `status_tone_for_status(row.status)`。 ### 下一步 下一步建议先部署到远端 smoke 容器并复跑: - `/api/v1/runtime/schema-check` - 合同创建、提交审批、退回、重提、发起审查抽样 - `display_cache_cleanup_prestate` 通过后再进入 `next_action/action_hint` 的语义设计,不要直接删列。 ## 2026-07-01 prestate 重新固化 本记录中的早期 smoke 基线基于“状态默认派生值”检查。2026-07-01 排查详情页日志后,确认 cleanup prestate 必须与真实读路径一致,也就是使用完整 `ContractActionContext`。 最新 smoke 结论: - Alembic actual/head:`20260701_0024` - `display_cache_consistency.checked_rows`:`12017` - 完整上下文口径 backfill 前 mismatch:`12006` - 已执行 backfill apply:`updated_rows=12006` - 回滚 snapshot:`/data/contract-files/display-cache-context-snapshot-20260701.json` - backfill 后 `display_cache_consistency.mismatch_count=0` - `display_cache_cleanup_prestate.status=ok` - `contract-api`、`contract-review-worker`、`contract-web` 均为 healthy 同时,`contract-api` Docker healthcheck 已从重型 `/api/v1/runtime/schema-check` 调整为轻量 `/api/v1/health`。`runtime/schema-check` 仍是 smoke/CI 质量门,但不再作为 5 秒容器探针,避免 12017 行完整上下文检查导致健康状态误报。 详细记录见: - `project-docs/V2-phase3-display-cache-context-prestate-2026-07-01.md`