Files
cmbuyer/docs/tasks/T-207.md
T

86 lines
6.3 KiB
Markdown
Raw Normal View History

---
id: T-207
title: 授权有效期与围栏前人工恢复
phase: 2
deps: [T-206]
status: TODO
created: 2026-08-04
vikunja_task_id: 46
context_ref: cd47d0c
work_branch: task/t-207-prefence-recovery
needs_device: false
needs_human_review: false
write_paths:
- docs/tasks/T-207.md
- admin/internal/taskrecovery/**
- admin/internal/taskdetail/**
- admin/internal/server/task_recovery.go
- admin/internal/server/task_recovery_test.go
- admin/internal/server/router.go
- admin/internal/transport/webui/webui.go
- admin/internal/transport/webui/templates/task-detail.html
- admin/cmd/server/**
- admin/README.md
- docs/api.md
- docs/routes.md
- docs/08-interaction-checklist.md
- docs/current-state.md
---
<!-- BEGIN VIKUNJA EXPORT id=46 synced=2026-08-04T14:44:03Z sha256=550593f5197409b814d6c2c2bca81d2d21c65bfc62abd1a3f20b107d837fb9e8 -->
## 问题 / 背景
T-205 的围栏前失败只把 attempt 置 FAILED、task 置 NEEDS_MANUAL,并刻意保留 authorization 与 claim;T-302 也明确租约丢失不等于手机停止。T-207 提供管理员人工检查后的围栏前恢复,使旧授权永久关闭后任务回到 DRAFT 或 CANCELED,同时保证任何围栏事实都永远不可释放。
## 关联需求与交互
- 功能:F-010、F-011、F-017 的围栏前恢复部分。
- 用户故事:US-005、US-008。
- 交互:IX-009、IX-010;T-206 负责状态与安全主动作,本任务接通被允许的写操作。
- 依赖:T-206;由其传递 T-205/T-302 的 claim、attempt 和详情事实。
- API:POST /tasks/{id}/reset-to-draft、POST /tasks/{id}/cancel。
- 后续消费者:T-208 submission fence 与恢复/围栏竞态测试。
## 方案
1. 管理写请求只接受有效管理员 session + CSRF 和有界严格表单;设备 Bearer 无权调用。请求绑定 expected_task_version、authorization_id、可选 expected_attempt_id、可选 expected_claim_generation、必要时的 confirmed_device_stopped 和安全 return_to。
2. 定义围栏前恢复资格:PENDING 且无 attempt/claim/submission 可关闭;NEEDS_MANUAL 且 T-205 attempt 已明确 FAILED、无 submission 可重置/取消;租约过期或设备撤销但 attempt 曾进入 CLAIMED/ORDERING 时,只有管理员明确确认设备已安全停止才可继续。活跃租约、仍在 ORDERING 且无明确停止事实时拒绝。
3. authorization/attempt 已 FENCED 或任何关联 order_submission 存在时永久拒绝,不受 task.status、租约、授权过期、人工确认或调和终态影响。数据归属缺失或互相冲突时 fail closed。
4. 在一个有界 SQLite 写事务中先取得写入线性化位置,再重新读取 task、authorization、attempt、claim 和 submission。首先检查 submission;随后精确比较版本、authorization、attempt 和 generation,最后校验恢复资格。
5. 成功只做状态关闭,不删除事实:过期 authorization 转 EXPIRED,其余围栏前关闭转 ABANDONED;CLAIMED/ORDERING attempt 可安全关闭为 ABANDONED,已 FAILED 保持 FAILED;claim 留存并只写 closed_at,恢复原因由 authorization/attempt/task 的固定终态组合表达,不新增自由文本字段;task 转 DRAFT 或 CANCELED、version 恰好加一。
6. 本任务不新增 migration。利用旧 authorization/attempt/claim 终止足迹和 task 版本形成自然幂等:相同旧版本与旧授权的相同终态请求稳定返回原成功;一旦出现新版本/新授权,旧请求必须冲突,不能关闭新授权。
7. 重置成功不创建新授权。重新开始必须回到列表,由管理员重新点击“开始采购(只创建待付款订单)”并通过 T-203 产生新版本、新授权。
8. T-206 详情仅在服务端计算确定可恢复时渲染 POST 表单。重置是高影响动作,明确说明旧授权永久关闭和必须重新开始;取消为独立后果,不把两者混为同一动作。
## 验收要点
- 覆盖无领取 PENDING、明确 FAILED、租约过期但未证明停止、人工停止确认、活跃 ORDERING 及归属冲突。
- 覆盖 expires_at 边界相等、旧 generation、task/version 漂移、不同 authorization/attempt 和设备撤销。
- 覆盖响应丢失、服务重启、同请求重放、同键异终态、reset/cancel 并发和新授权后旧请求重放。
- 覆盖恢复与模拟围栏插入并发:只要任一 order_submission 已存在或先提交,恢复必须零释放;已 MANUAL_RESOLVED 也仍拒绝。
- 成功后 authorization、claim、attempt、events、evidence 全部保留;只改变获准状态,不删除审计事实。
- 管理/设备身份隔离、CSRF、请求大小/字段、错误不泄露均有测试。
- `go test ./...`、`go test -race ./...`、`go vet ./...`、`go build ./...`、完整 init、上下文、Vikunja export 与 diff-check 通过。
## 执行记录
(暂无)
<!-- END VIKUNJA EXPORT -->
## 边界
- 只有有效管理员 session + CSRF 的显式人工动作可以执行围栏前重置或取消;设备 Bearer、后台过期
扫描、租约超时、设备撤销、停止轮询和进程重启都不得自动关闭授权或把任务送回 `DRAFT`。
- 租约过期或设备撤销不证明手机已停止。曾进入 `CLAIMED` / `ORDERING` 且没有 T-205 明确失败事实
的 attempt,必须由管理员明确确认设备已安全停止;活跃执行或事实不完整时一律拒绝恢复。
- 事务必须先取得写入线性化位置,并把任何关联 `order_submission` 的存在作为最高优先级硬拒绝。
authorization/attempt 已 `FENCED`,或 submission 已 `FENCED`、`SUBMITTED`、
`RECONCILIATION_REQUIRED`、`MANUAL_RESOLVED`,均不得释放、取消、重置或重新授权。
- 恢复只能关闭既有围栏前状态并保留全部审计事实;不得删除 authorization、claim、attempt、event、
evidence 或 submission。重置不得创建新授权,重新开始必须由管理员重新执行 T-203。
- 自然幂等必须绑定旧 task version、旧 authorization、预期 attempt/generation 和精确终态;不能仅因
当前已是 `DRAFT` / `CANCELED` 就返回成功。出现新版本或新授权后,旧请求必须冲突,不能影响新事实。
- 本任务不建立、查询为许可或模拟 submission fence,不实现设备 result、人工 submission 调和、
真机动作、点击“提交订单”或任何支付、免密支付、先用后付和扣款能力。