a3bb78e06b
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Failing after 16m35s
143 lines
4.1 KiB
Markdown
143 lines
4.1 KiB
Markdown
# 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 和读路径都不再依赖旧列。
|