# AI 合同平台 V2 Phase 3 display cache 完整上下文一致性收口 ## 背景 本记录承接 Phase 3 UI 展示字段派生和旧 display cache 治理工作。 上一轮 smoke 中出现过一个容易误判的现象: - `/api/v1/runtime/schema-check` 中 `display_cache_consistency.mismatch_count = 0`。 - 但合同详情页渲染期间,Contract API 日志仍出现 `Contract display cache mismatch; derived value will be used`。 排查后确认这不是单纯日志噪音,而是治理检查口径与真实读路径口径不一致。 ## 根因 合同详情页、合同列表、workspace 和搜索读路径使用完整 `ContractActionContext` 派生展示字段。该上下文会纳入: - 最新审查任务状态。 - 结构化风险项和条款修复状态。 - 审批实例和待办步骤。 - 履约任务。 - `action_hint`。 旧的 `display_cache_governance` 只按 `contracts.status` 与旧缓存列做对比,没有加载上述上下文。因此会出现“schema-check 全绿,但详情页按完整上下文派生时仍 warning”的假一致。 ## 本次修正 本次将 display cache 治理口径升级为完整上下文口径: - `display_cache_governance.iter_display_cache_rows(...)` 通过 SQL 读取最新审查、结构化风险、审批、履约和 `action_hint`。 - `display_cache_mismatch_for_row(...)` 调用同一个 `derive_contract_display(...)`,并传入 `ContractActionContext`。 - `schema_check` 在缺少上下文表或 `contracts.action_hint` 时跳过 display cache assessment,避免旧库结构下误报连接异常。 - `contract-api` Docker healthcheck 改为轻量 `/api/v1/health`。重型 `/api/v1/runtime/schema-check` 继续作为 smoke/CI 质量门,而不再作为 5 秒容器健康探针。 这修正了前一份 `V2-phase3-search-action-context-audit-2026-06-30.md` 中“display_cache_governance 保持轻量、不加载 workflow context”的阶段性判断。新的 smoke 证据表明:cleanup prestate 要判断旧缓存列是否真的安全,必须使用与用户可见读路径一致的完整上下文。 ## Smoke 验证 远端环境: - Host:`192.168.3.90` - Contract API:`http://192.168.3.90:18100` - Contract Web:`http://192.168.3.90:13000` - Database:`yuqei_contract` - Alembic actual/head:`20260701_0024` 修正治理口径后,首次复跑: ```json { "display_cache_consistency": "warning", "checked_rows": 12017, "mismatch_count": 12006, "field_counts": { "next_action": 12006, "next_actions": 12006 } } ``` 抽样合同: - `contract-0ba3bd5b04b6` - 修复前旧缓存:`根据审查结果修改或提交审批` - 完整上下文派生:`处理审查风险并应用推荐条款` 这说明旧缓存主要不是状态标签问题,而是 `next_action/next_actions` 尚未按最新审查风险和条款推荐上下文刷新。 ## Backfill 已在远端 smoke 执行可回滚 backfill: ```json { "command": "backfill", "mode": "apply", "updated_rows": 12006, "snapshot": "/data/contract-files/display-cache-context-snapshot-20260701.json" } ``` 回滚 snapshot: - 容器路径:`/data/contract-files/display-cache-context-snapshot-20260701.json` - 宿主卷路径:`/var/lib/docker/volumes/yuqei-contract-v2-smoke_contract-files/_data/display-cache-context-snapshot-20260701.json` - 文件大小:约 `5.0M` 如需回滚 smoke 数据: ```bash cd /opt/apps/ai-contract-platform-v2-smoke/yuqei-infra/compose docker compose --env-file .env.smoke -p yuqei-contract-v2-smoke run --rm contract-api \ python -m yuqei_contract_api.display_cache_governance_cli rollback \ --apply \ --snapshot /data/contract-files/display-cache-context-snapshot-20260701.json ``` ## Backfill 后结果 复跑 `/api/v1/runtime/schema-check`: ```json { "status": "ok", "display_cache_consistency": { "status": "ok", "checked_rows": 12017, "mismatch_count": 0 }, "display_cache_cleanup_prestate": { "status": "ok", "legacy_columns_retained": true, "display_cache_consistent": true } } ``` 复跑治理 CLI: ```json { "command": "assess", "checked_rows": 12017, "mismatch_count": 0, "field_counts": {}, "samples": [] } ``` 服务状态: - `contract-api`:healthy - `contract-review-worker`:healthy - `contract-web`:healthy 页面抽样: - `/contracts/contract-0ba3bd5b04b6` 返回 `200`。 - 页面仍展示“可应用条款”推荐卡片。 - 本次请求后 `Contract display cache mismatch` 日志计数为 `0`。 ## 本地验证 ```powershell python -m py_compile ` yuqei-contract-platform\services\contract-api\src\yuqei_contract_api\display_cache_governance.py ` yuqei-contract-platform\services\contract-api\src\yuqei_contract_api\schema_check.py python -m pytest yuqei-contract-platform\services\contract-api\tests\test_display_cache_governance.py -q python -m pytest yuqei-contract-platform\services\contract-api\tests\test_health.py -q ``` 结果: - `test_display_cache_governance.py`:5 passed - `test_health.py`:10 passed 新增回归覆盖: - `test_assess_display_cache_consistency_uses_review_action_context` 该测试构造“合同状态默认缓存看似一致,但最新审查有未修复风险”的场景,确认治理检查会按完整上下文发现 `next_action/next_actions` mismatch。 ## 当前结论 当前 smoke 已重新固化为: - 旧 display cache 列仍保留。 - 旧列值已按完整业务上下文与 domain/serializer 派生结果一致。 - `display_cache_cleanup_prestate` 可作为后续停写/删列前的保护门。 - 不建议立即删列。 ## 下一步建议 下一片仍应走保守路径: 1. 先继续停写旧 `contracts.next_action / contracts.next_actions` 的生产写路径。 2. 保留 API 响应字段由 `derive_contract_display(...)` 输出。 3. 重建搜索索引或增量同步时继续使用完整 `ContractActionContext`。 4. 连续 smoke 多轮保持 `display_cache_consistency=ok` 后,再评审是否进入 cleanup migration。 不要把 `status_label/status_tone` 与 `next_action/next_actions` 一次性删掉。前者是纯展示缓存,后者仍承担动作上下文兼容面,必须分片处理。 ## 后续停写进展 2026-07-01 后续小片已开始停写旧 `contracts.next_action / contracts.next_actions` 生产写路径。 新的口径是: - `status_label/status_tone` 继续作为兼容缓存保持一致。 - `next_action/next_actions` 旧列仍保留,但不再要求跟随完整上下文派生值写回。 - API 响应和搜索展示继续由完整 `ContractActionContext` 派生。 详细记录见: - `project-docs/V2-phase3-stop-legacy-next-action-writes-2026-07-01.md`