diff --git a/docs/task/254-运行时规格解析契约与审计模型.md b/docs/task/254-运行时规格解析契约与审计模型.md new file mode 100644 index 0000000..8e60f5b --- /dev/null +++ b/docs/task/254-运行时规格解析契约与审计模型.md @@ -0,0 +1,93 @@ +# 254 Admin:定义采购运行时规格解析接口与审计模型 + +- 类型:需求 / 重构 +- 父级大工单:#96 +- 所属 MVP / 版本:#253 采购运行时 AI 规格纠偏闭环 +- 状态:已完成,待用户验收 +- 日期:2026-08-17 +- Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/254 + +## 背景与目标 + +Client 在真实采购规格面板找不到任务尺码时,原流程只能提交失败。为了让同一次、尚未 +进入不可逆阶段的采购可以取得受审计的 Admin 规格决策,本任务先冻结第五个 Client API +契约,并建立保存候选观察和最终决策的最小数据模型。 + +本任务不实现 AI 匹配、HTTP Handler、Client 真机候选采集或采购续跑,也不复用会改变 +任务状态的失败提交接口。 + +## 最终方案 + +- 定义一次性命令 `POST /api/v1/client/tasks/{task_id}/spec-resolution`,不增加 GET、 + 状态轮询、租约或心跳。 +- 请求 schema v1 只包含任务/执行身份、PDD 商品、原任务规格、已选颜色、目标尺码、 + 当前颜色下最多 100 条可购买候选、快照哈希和观测时间;整个请求最多 64 KiB。 +- 候选使用连续短编号 `c1`~`c100`。快照和幂等键都使用明确的 UTF-8 长度前缀 + SHA-256 算法,Admin 必须重新计算,不能信任 Client 自报哈希。 +- 响应固定为 `matched/uncertain/rejected/failed`。只有 matched 返回请求内候选的逐字 + 副本;来源为 `rule/ai/reused/null`,置信度使用可空的 0~10000 基点。 +- MySQL v26 新增一张 `purchase_spec_resolutions`:同一行保存 pending 候选观察和最终 + 决策,业务唯一键为 `(task_id, attempt_id, candidate_snapshot_hash)`,另存 + `request_hash` 识别同一身份的不同内容。 +- Repository 提供新增、按解析编号读取、按业务身份读取、幂等复查和只完成一次决策。 + 候选观察先用短事务落库,规则/AI 网络调用不得占用事务;最终决策和可重放响应由后续 + Handler/Service 在另一个短事务与 `idempotency_keys` 一起提交。 +- 审计表不设任务和 Client 外键,避免既有硬删除流程阻塞或级联清除审计;不修改任务 + 状态、`pdd_products.skus_json` 或长期 `spec_mappings`。 +- 表中不保存原始无障碍 XML、截图、订单号、收货信息、Cookie、Token、密码、API Key、 + 完整提示词或完整模型响应。 + +最终实现与工单方案一致。实施时进一步明确了跨 Python/Go 可复现的长度前缀哈希算法, +以及“短事务保存观察 → 事务外解析 → 短事务完成决策”的崩溃恢复边界。 + +## 改了哪些 + +- `admin/model/model.go` + - 增加解析 outcome/source 枚举、完整审计模型和完成决策模型。 +- `admin/repository/mysql_db.go` + - schema 版本升级到 v26,增加解析审计表、约束、索引和启动自检。 +- `admin/repository/purchase_spec_resolution.go` + - 增加候选观察新增、读取、幂等复查和只完成一次的参数化 Repository。 +- `admin/repository/mysql_db_integration_test.go` + - 覆盖 v25→v26、重复迁移、业务唯一键、请求冲突、决策只完成一次和候选上限。 +- `docs/client/04-admin-api-contract.md` + - 写入权威 HTTP schema、限制、哈希、响应、错误、兼容和隐私边界。 +- `docs/admin/04-client-api.md` + - 同步 Admin 处理顺序、事务边界、稳定错误和实现清单。 +- `docs/client/03-data-model.md` + - 明确 Client 后续持久化边界、原任务不可覆盖和旧 Client 兼容行为。 +- `docs/admin/03-data-model.md` + - 记录 MySQL v26 表结构、约束、幂等关系和数据安全边界。 + +## 验收结果 + +| 验收标准 | 结果 | +|---|---| +| Client 和 Admin 文档的请求、响应、错误、幂等和兼容行为一致 | 通过 | +| 新调用是一次性业务命令,无状态查询、轮询、租约或心跳 | 通过 | +| 请求不含原始 XML、订单、收货信息和凭据,并限制字段、100 条候选和 64 KiB | 通过 | +| 任务、尝试和候选快照唯一定位记录,相同请求可读取同一记录 | 通过 | +| MySQL 8.4 全新建库、v25 升级和重复迁移通过,自检后才记录 v26 | 通过 | +| 可表达规则、AI、复用、不确定、拒绝和失败,置信度保留 NULL | 通过 | +| 不更新 PDD 主数据、任务状态和原四个 Client API | 通过 | +| Go 1.23.0 build、test、vet 和格式检查 | 通过 | + +## 测试 + +- 执行的命令: + - `gofmt -l .` + - `$env:GOTOOLCHAIN='go1.23.0'; go build ./...` + - `$env:GOTOOLCHAIN='go1.23.0'; go test ./... -count=1` + - `$env:GOTOOLCHAIN='go1.23.0'; go vet ./...` + - 配置指向本机临时隔离的 MySQL 8.4 `_test` 库后: + `go test ./repository -run 'TestMySQLMigrate_(真实MySQL8|V25升级V26且规格解析审计幂等)$' -count=1 -v` +- 结果: + - Go 1.23.0 格式、构建、全量测试和 vet 全部通过。 + - MySQL Community Server 8.4.8 实际通过全新建库、v25→v26、重复迁移和 Repository + 幂等/约束集成测试;测试使用临时 `_test` 库,未连接或修改生产库。 +- **没验证到的部分**:无。HTTP Handler、AI 调用和 Client 真机行为属于 #255~#258, + 不在本工单范围。 + +## 相关提交 + +- `1241465` 定义运行时规格解析契约与审计模型