docs: import wiki at afc651f75a3a
+224
@@ -0,0 +1,224 @@
|
||||
<!-- docs-wiki-sync:docs/tasks/T-258.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||
> 同步来源:[`docs/tasks/T-258.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-258.md) · commit `afc651f75a3a`
|
||||
|
||||
---
|
||||
id: T-258
|
||||
title: 后端放宽候选批次数量门槛以与 T-256 对齐
|
||||
phase: 2
|
||||
deps:
|
||||
- T-256
|
||||
- T-257
|
||||
status: DOING
|
||||
created: 2026-07-31
|
||||
context_ref: 42a56ea
|
||||
work_branch: null
|
||||
write_paths:
|
||||
- docs/tasks/T-258.md
|
||||
- docs/api.md
|
||||
- docs/current-state.md
|
||||
- backend-api/internal/usecase/**
|
||||
- backend-api/internal/usecase/execution_result_service_test.go
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
T-256 放宽了 **Android 侧**的候选提交门槛(第三轮结束时至少一个已解析候选即可提交),
|
||||
但**后端仍在执行旧的「必须凑够 2 个」规则**,两侧口径不一致,导致真机任务永久卡死。
|
||||
|
||||
`backend-api/internal/usecase/execution_result_service.go:735-737`:
|
||||
|
||||
```go
|
||||
return !command.CollectionComplete ||
|
||||
exact == 2 ||
|
||||
(command.SearchRound == 3 && fallback == 2)
|
||||
```
|
||||
|
||||
即 `collection_complete=true` 时必须恰好 2 个 `EXACT_MATCH`,或第三轮恰好 2 个
|
||||
`FALLBACK`,否则整批拒收。
|
||||
|
||||
### 真机实测的死锁
|
||||
|
||||
execution `9c634321-7f77-49d9-9444-3ccd2104c104`,设备侧状态:
|
||||
|
||||
```
|
||||
attempt_count = 3 三轮跑完
|
||||
terminal_queued = false 未写失败终态
|
||||
completed = true 门禁标记成功完成
|
||||
候选数 = 1:ordinal=1 round=1 EXACT_MATCH goodsId=970618634860 双证据齐全
|
||||
```
|
||||
|
||||
设备 logcat:
|
||||
|
||||
```
|
||||
ProcurementApiException: 后台拒绝了请求
|
||||
at ProcurementApiClient.uploadExecutionOutboxItem(ProcurementApiClient.kt:333)
|
||||
at ProcurementRepository.flushExecutionOutbox(ProcurementRepository.kt:1675)
|
||||
```
|
||||
|
||||
结果是双方都认为自己没错:
|
||||
|
||||
| 端 | 状态 |
|
||||
| --- | --- |
|
||||
| App | `completed=true`,不再写失败终态,outbox 无限重试上传 |
|
||||
| 后端 | 持续拒收整批,从未收到候选 |
|
||||
| Admin | 任务永久停在 `RUNNING`,不会自行恢复 |
|
||||
|
||||
重跑无效——换一个 execution 仍然只有 1 个候选,仍然被拒。
|
||||
|
||||
### 契约文档与实现不符
|
||||
|
||||
`docs/api.md` 关于 `collection_complete` 只写了:
|
||||
|
||||
> `collection_complete=true` 才在同一事务中生成决策数据集、记录 `CANDIDATES_READY`
|
||||
> 并将任务切换到 `WAITING_CONFIRMATION`。
|
||||
|
||||
**完全没有提到「必须 2 个」这条限制。** 后端实际执行的规则比文档严格,属于未文档化的
|
||||
隐式约束,本任务一并修正。
|
||||
|
||||
### 与需求的冲突
|
||||
|
||||
`docs/02-requirements.md`:
|
||||
|
||||
- 范围决策:「以 SKU 颜色/尺码硬约束过滤后回传 **`0..5` 个**」
|
||||
- 业务规则 20:「回传可以少于 5 个,禁止用弱匹配凑数」
|
||||
|
||||
需求要求的是「不足就少回传」,后端实现成了「不足就整批拒收」。
|
||||
|
||||
## 关联需求与交互
|
||||
|
||||
- 功能:F-005、F-006、F-007
|
||||
- 用户故事:US-004、US-005
|
||||
- 交互:沿用 Admin 候选确认区,不新增页面
|
||||
- 架构/API:**修改 `POST /api/v1/tasks/{task_id}/candidates` 的校验规则**,
|
||||
`docs/api.md` 必须同步
|
||||
|
||||
## 行为契约
|
||||
|
||||
### 一、放宽数量门槛
|
||||
|
||||
`validIncrementalCandidateSet` 的最终返回改为:`collection_complete=true` 时
|
||||
**至少一个带 `match_type` 的已解析候选**即可接收,不再要求恰好 2 个。
|
||||
|
||||
`annotated == 0` 的既有提前返回**保持不变**(零候选批次仍然接受,语义为「搜索完成但
|
||||
无匹配」,与需求允许回传 `0..5` 个一致)。
|
||||
|
||||
### 二、防凑数上限一律保留
|
||||
|
||||
以下既有校验**一条都不能放松**:
|
||||
|
||||
```go
|
||||
annotated != len(command.Candidates) ||
|
||||
exact > 2 || fallback > 2 || annotated > 3 ||
|
||||
(fallback > 0 && command.SearchRound != 3) ||
|
||||
(exact == 2 && fallback > 0)
|
||||
```
|
||||
|
||||
放宽的只是**下限**,上限、轮次归属和 EXACT/FALLBACK 互斥全部维持原状。
|
||||
|
||||
### 三、其余校验不变
|
||||
|
||||
`validCandidate`、`validCandidateCollectionMetadata`、`validPinduoduoCandidateProduct`、
|
||||
`validEvaluation`、`validSKUMatchedCandidate`、goods_id 去重、证据 asset 哈希核对
|
||||
全部保持现状,一个字节都不改。
|
||||
|
||||
### 四、契约文档同步
|
||||
|
||||
`docs/api.md` 中 `collection_complete` 段落必须补明实际规则:接收 `1..3` 个带
|
||||
`match_type` 的候选(`EXACT_MATCH` 至多 2 个且只能来自前两轮,`FALLBACK` 至多 2 个且
|
||||
只能来自第三轮,两者不得同时达到上限),不再要求达到某个固定数量。
|
||||
|
||||
### 五、卡死任务的恢复
|
||||
|
||||
修复后无需重跑真机任务。App 的 outbox 仍在重试上传,后端一旦接收,execution
|
||||
`9c634321-7f77-49d9-9444-3ccd2104c104` 应自动从 `RUNNING` 收敛到
|
||||
`WAITING_CONFIRMATION`,候选 `goodsId=970618634860` 出现在确认区。这是本任务最直接的
|
||||
验收信号。
|
||||
|
||||
## 方案
|
||||
|
||||
1. 修改 `execution_result_service.go` 中 `validIncrementalCandidateSet` 的最终返回。
|
||||
2. 补充后端单测:1 个 EXACT_MATCH + `collection_complete=true` 被接收;
|
||||
0 个 annotated 仍被接收;`exact > 2`、`fallback > 2`、`annotated > 3`、
|
||||
FALLBACK 出现在非第三轮、`exact == 2 && fallback > 0` 全部仍被拒绝。
|
||||
3. 更新 `docs/api.md` 的 `collection_complete` 说明。
|
||||
4. 全量跑 `go test ./...` 确认没有依赖旧规则的既有测试被破坏——如有,逐个确认是
|
||||
规则变更的预期影响,并在执行记录中列明。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] 1 个 `EXACT_MATCH` 候选 + `collection_complete=true` 被后端接收,任务转
|
||||
`WAITING_CONFIRMATION`。
|
||||
- [ ] 0 个 annotated 候选的批次行为与本任务前一致。
|
||||
- [ ] `exact > 2`、`fallback > 2`、`annotated > 3` 仍被拒绝。
|
||||
- [ ] `FALLBACK` 出现在非第三轮仍被拒绝。
|
||||
- [ ] `exact == 2 && fallback > 0` 仍被拒绝。
|
||||
- [ ] goods_id 重复、证据哈希不符、`match_type` 与轮次不符仍被拒绝,有回归测试。
|
||||
- [ ] `docs/api.md` 的 `collection_complete` 说明与实现一致。
|
||||
- [ ] `GOTOOLCHAIN=local go test ./...` 在 `backend-api` 全部通过。
|
||||
- [ ] 根 `init.ps1` 通过(本任务涉及后端,该流程适用)。
|
||||
- [ ] 真机验证:无需重跑任务,execution `9c634321-7f77-49d9-9444-3ccd2104c104`
|
||||
自动收敛到 `WAITING_CONFIRMATION`,候选 `goodsId=970618634860` 出现在 Admin
|
||||
候选确认区且可选择。
|
||||
|
||||
## 边界
|
||||
|
||||
- **只放宽下限,不动上限**:`exact > 2`、`fallback > 2`、`annotated > 3`、轮次归属和
|
||||
EXACT/FALLBACK 互斥全部保留。
|
||||
- 不改 `validCandidate`、`validCandidateCollectionMetadata`、
|
||||
`validPinduoduoCandidateProduct`、`validEvaluation`、`validSKUMatchedCandidate`。
|
||||
- 不改证据 asset 绑定、哈希核对、goods_id 去重或截图受控留存。
|
||||
- 不改任务状态机、租约、claim、heartbeat 或取消逻辑。
|
||||
- 不改下单授权、订单 dry-run、授权价格策略或支付边界。
|
||||
- 不改数据库 schema,不加迁移。
|
||||
- **不改 Android 任何代码**——T-256 已完成客户端侧,本任务只补后端。
|
||||
- 不改 Admin 模板或候选展示。
|
||||
- 不为绕过校验而放宽 SKU 颜色/尺码硬约束。
|
||||
|
||||
## 执行记录
|
||||
|
||||
开工后记录修改、命令、结果、环境、决策和 blocker。
|
||||
|
||||
### 2026-07-31
|
||||
|
||||
- 状态:开工时由 `TODO` 改为 `DOING`;按任务要求未改为 `DONE`。
|
||||
- 修改:
|
||||
- `validIncrementalCandidateSet` 在原有提前返回和完整上限校验通过后直接接收,不再
|
||||
要求完成快照恰好含 2 个 `EXACT_MATCH` 或第三轮恰好含 2 个 `FALLBACK`。
|
||||
- 原 `annotated == 0` 行为保持不变;`annotated != len(candidates)`、`exact > 2`、
|
||||
`fallback > 2`、`annotated > 3`、第三轮外 `FALLBACK` 和
|
||||
`exact == 2 && fallback > 0` 的拒绝表达式逐字保留。
|
||||
- 将既有“单个 exact 完成快照应拒绝”测试改为接受断言。这是本次下限契约变更的
|
||||
预期影响,不是为规避失败而删断言;同时新增单个第三轮 fallback 接受、零候选既有
|
||||
接受、全部六类上限/轮次/互斥拒绝、match type 轮次归属和 `goods_id` 去重回归测试。
|
||||
- `docs/api.md` 已补明:完成快照无需凑满固定数量,带 `match_type` 的已解析批次
|
||||
接收 `1..3` 个且必须全部标注,零候选表示搜索完成但无匹配,并完整记录
|
||||
exact/fallback 上限、轮次和混合限制。
|
||||
- `docs/current-state.md` 已同步实现、验证结果和待真机验证 blocker。
|
||||
- 明确保留的边界:未修改 `validCandidate`、`validCandidateCollectionMetadata`、
|
||||
`validPinduoduoCandidateProduct`、`validEvaluation`、`validSKUMatchedCandidate`、
|
||||
evidence asset/hash 校验或 `goods_id` 去重;未修改数据库 schema/migration、Android、
|
||||
Admin、下单或支付代码。
|
||||
- 命令与结果(Windows PowerShell):
|
||||
- 在 `backend-api` 工作目录运行 `$env:GOTOOLCHAIN='local'; go test ./internal/usecase`
|
||||
(修改前基线):退出码 0,`ok`(cached)。
|
||||
- `gofmt -w internal/usecase/execution_result_service.go internal/usecase/execution_result_service_test.go`:
|
||||
退出码 0。
|
||||
- `$env:GOTOOLCHAIN='local'; go test ./internal/usecase`(修改后):退出码 0,
|
||||
`ok cmroubao/backend-api/internal/usecase 0.185s`。
|
||||
- 在 `backend-api` 工作目录运行 `$env:GOTOOLCHAIN='local'; go test ./...`:
|
||||
退出码 0,所有 Go package 通过;没有因本次规则变化而失败的其他既有 Go 测试。
|
||||
- 在仓库根目录运行 `.\init.ps1`:退出码 0;Android 单测和 Debug APK 构建
|
||||
`BUILD SUCCESSFUL`(78 tasks),后端全量 Go 验证通过。输出包含既有 SDK XML
|
||||
版本兼容 warning,不影响成功结果。
|
||||
- `git diff --check`:退出码 0,无 whitespace error。
|
||||
- 对本任务全部已修改文件执行 CRLF 字节扫描:退出码 0,结果 `LF_ONLY`。
|
||||
- 既有回归证据:全量 Go suite 中
|
||||
`TestDeviceExecutionResultsAreIdempotentAndAuditable` 继续覆盖 evidence asset hash
|
||||
不符拒绝、非完成快照保持 `RUNNING`、完成快照产生单个 `CANDIDATES_READY` 并切换
|
||||
`WAITING_CONFIRMATION`;本任务没有修改该测试或其实现路径。
|
||||
- 环境:工作区 `D:\chengma\cmroubao`,Go 使用 `GOTOOLCHAIN=local`;未运行 git
|
||||
commit 或 push,未改动工作区中既有的无关未跟踪文件。
|
||||
- 未验证 / blocker:无法执行最终真机验证。仍需观察 execution
|
||||
`9c634321-7f77-49d9-9444-3ccd2104c104` 的既有 outbox 在部署修复后自动上传,
|
||||
任务收敛到 `WAITING_CONFIRMATION`,并确认 `goodsId=970618634860` 出现在 Admin
|
||||
候选确认区;因此任务保持 `DOING`。
|
||||
Reference in New Issue
Block a user