# AI 合同平台 V2 Phase 3 拒绝旧 next_action 输入 ## 背景 30 分钟 smoke 观察窗口显示旧 `next_action / next_actions` alias 没有新增调用: - `baseline_total=1` - `final_total=1` - `observed_delta=0` - `relevant_new_count=0` 因此进入 cleanup 的 Slice A:先收紧接口输入,不动数据库结构。 ## 本次调整 ### PATCH 输入收紧 合同更新接口: ```text PATCH /api/v1/contracts/{contract_id} ``` 现在行为: - `action_hint`:继续支持,作为人工跟进提示唯一写入字段。 - `next_action`:拒绝,返回 `422`。 - `next_actions`:拒绝,返回 `422`。 错误响应示例: ```json { "detail": { "code": "deprecated_contract_update_action_alias", "message": "Deprecated contract action fields are no longer accepted. Use action_hint for manual follow-up text.", "fields": ["next_action", "next_actions"], "replacement_field": "action_hint" } } ``` ### 观测保留 旧字段被拒绝时仍会写入观测窗口: - `alias_group=contract_update_action` - `fields=["next_action","next_actions"]` - `target_field=action_hint` - `compatibility_status=rejected` 观测记录仍不保存旧字段文本值。 ### 不动数据库结构 本片没有执行 Alembic migration。 继续保留: - `contracts.next_action` - `contracts.next_actions` 原因: - 响应字段 `next_action / next_actions` 仍由派生层对外保留。 - 部分 read/search/schema-check 路径仍把旧列作为 retained compatibility columns。 - 真正删列应放到后续 Slice C。 ## OpenAPI `ContractUpdateRequest.next_action / next_actions` 描述已更新为: - deprecated。 - no longer accepted by write paths。 - use `action_hint` instead。 ## 验证 ```powershell python -m pytest ` yuqei-contract-platform\services\contract-api\tests\test_contract_workflow.py::test_contract_update_accepts_action_hint_and_rejects_legacy_next_action ` yuqei-contract-platform\services\contract-api\tests\test_contract_workflow.py::test_contract_update_legacy_action_alias_usage_is_observable ` -q ``` 预期: - `action_hint` PATCH 返回 200。 - `next_action / next_actions` PATCH 返回 422。 - 旧字段文本值不会进入错误响应或 metrics。 - metrics 中 `compatibility_status=rejected`。 本地结果: - 聚焦回归:2 passed。 - `py_compile`:通过。 - `test_health.py` + `test_monitoring.py`:17 passed。 - OpenAPI YAML 解析:`3.1.0 / 51 paths`。 ## 远端 smoke 验证 环境: - 宿主机:`192.168.3.90` - Contract API:`http://127.0.0.1:18100` - compose project:`yuqei-contract-v2-smoke` 部署结果: - `contract-api` 已重建并启动。 - `contract-api` 容器状态:healthy。 - `/api/v1/runtime/schema-check` 返回 `status=ok`。 - `display_cache_consistency.mismatch_count=0`。 - `display_cache_cleanup_prestate.status=ok`。 - 旧列仍保留:`retained_compat_fields=next_action,next_actions`。 接口抽样: - smoke 合同:`contract-e51a2edfa899` - `action_hint` PATCH:返回 200。 - 旧 `next_action / next_actions` PATCH:返回 422。 - 错误 code:`deprecated_contract_update_action_alias`。 - 合同详情仍保持:`action_hint=supported-action-hint-smoke`。 - 响应派生字段仍返回:`next_action=supported-action-hint-smoke`、`next_actions=["编辑合同","提交审批"]`。 - metrics 记录: - `alias_group=contract_update_action` - `compatibility_status=rejected` - `fields=["next_action","next_actions"]` - `trace_id=trace-reject-legacy-alias-smoke` - `tenant_id=tenant-reject-legacy-alias-smoke` - 旧字段文本值未进入错误响应或 metrics。 - 最近 10 分钟 `Deprecated contract update action alias used` 日志计数为 1。 - 最近 10 分钟 `Contract display cache mismatch` 日志计数为 0。 - 3 秒观察窗口通过:`baseline_total=1`、`final_total=1`、`observed_delta=0`、`relevant_new_count=0`。 ## 下一步 后续建议: - 再跑一个 30 分钟观察窗口,确认没有真实旧客户端继续调用。 - 如果窗口仍为 0,进入 Slice B:停止旧列读取。 - 继续暂缓 Slice C 数据库删列,直到 schema-check 和读路径都不再依赖旧列。