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

94 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-400
title: 离线单次提交安全编排
phase: 4
deps: [T-305, T-308]
status: TODO
created: 2026-08-04
vikunja_task_id: 50
context_ref: 8600c33
work_branch: task/t-400-offline-submission-orchestrator
needs_device: false
needs_human_review: true
write_paths:
- docs/tasks/T-400.md
- client/src/cmbuyer_client/execution/submission_orchestrator.py
- client/tests/execution/test_submission_orchestrator.py
---
<!-- BEGIN VIKUNJA EXPORT id=50 synced=2026-08-04T15:04:36Z sha256=4f27bf3ca322f467cb742da1b9159cbb976f6939c0cd804021323a24fa4cc735 -->
## 问题 / 背景
T-305 的正常 dry-run 会在同一设备会话完成三闸门与最终控件只读观察,随后执行一次安全 Back,并以固定 reason DRY_RUN_COMPLETED_BEFORE_FENCE 终止 attempt。它只证明围栏前代码和判据契约已验收,绝不产生可供真实提交复用的运行时 ReadyForSubmission。T-308 将提供严格 fence/result sink、当前进程唯一 permit 和不可恢复的点击意图能力。T-400 只用测试 fixture 验证纯 SinglePassSubmissionOrchestrator、四条件、一次性调用和崩溃闭包;不读取或复用 T-305 的对象、截图、证据、journal、缓存或旧 proof,不提供生产 ReadyForSubmission 构造入口,也不接入 app/UI/PDD/真机。
## 关联需求与交互
- 功能:F-006、F-007、F-011、F-017 的离线提交安全闭包。
- 用户故事:US-003、US-005、US-008。
- 交互:IX-011 的后台顺序;本任务无生产界面。
- 依赖:T-305、T-308。T-305 只提供已验收的代码/判据契约,T-308 提供冻结接口;两者都不向本任务提供可提交的运行时事实。
- 后续消费者:T-401 在全新真实任务、全新 attempt 的同一趟内重跑全部三闸门并即时构造生产 ReadyForSubmission。
- 生产 app 的 import/call graph 在 T-401 前继续不可达本任务编排、ReadyForSubmission 生产构造和 SubmitOrderPort。
## 方案
1. 在单一窄 execution 模块实现纯 SinglePassSubmissionOrchestrator、只读 ReadyForSubmission 协议和抽象 SubmitOrderPort;只消费 T-308 已冻结的 SubmissionSink 接口,不修改 submissions/remote/localstate/core/PDD/UI/app。
2. T-305 的 PreFenceDryRunOrchestrator、终止结果、action journal、AssetRef、截图、缓存、页面对象、attempt 或 proof 不能作为 ReadyForSubmission 输入。T-305 已 Back 且终止的 attempt 永远不能申请 fence、取得 permit 或进入提交编排。
3. 本任务不提供任何生产 ReadyForSubmission 具体类、公共构造器、factory、adapter 或 from_dry_run/from_snapshot/from_evidence/from_localstate 路径。只有 tests 中的 immutable fixture 实现该只读协议;生产包内不存在把真实或旧事实装配成 ReadyForSubmission 的入口。
4. 测试 fixture 必须显式给出一个虚构但内部一致的同一 task/authorization/attempt/generation/App 版本事实:未消费授权快照、Gate2/Gate3 通过、精确规格/数量/金额、三个受控 AssetRef 和提交文本/唯一计数。它只用于纯逻辑测试,不是页面证据或可发布业务事实。
5. 编排在申请 fence 前重新校验 fixture 表达的四条件:一次性授权/claim 一致且未过期、Gate2 通过、Gate3 通过、submit 文本精确且唯一计数为一。任一缺失、漂移、金额不规范或版本不一致均零 fence、零 SubmitOrderPort 调用。
6. 只有测试注入的 T-308 当前进程首次明确 SubmissionPermit 可继续。先调用 prepare_click_intent durable 写入唯一点击意图并取得一次性 ArmedSubmission;未成功 arm、重放许可、LOOKUP_ONLY、并发进入或重启恢复均零 SubmitOrderPort 调用。
7. SubmitOrderPort 只暴露 submit_once(ArmedSubmission, VerifiedSubmitObservationBoundary)。编排层最多调用一次,不提供 retry、循环、回退、第二调用点或可重复 submit 函数;本任务只注入记录次数/故障点的 fake port,没有具体真机适配器。
8. 调用前已经进入不可逆 reconcile-only 状态。fake port 返回、抛错、超时、进程停止或结果不明都不能回到可提交状态;异常只形成 UNKNOWN 待调和事实,不再次调用 port。
9. fake port observation 只允许 SUBMITTED、EXTERNAL_PAYMENT_HANDOFF、SECURITY_CHALLENGE、UNKNOWN。编排通过 T-308 固定接口只上报一次 result;HTTP 不明只恢复原 key/载荷,不重复页面观察或提交动作。本任务不实现或测试任何基于真实页面的 SUBMITTED 推断。
10. 停止轮询、Esc、查看历史、关闭窗口和重启不是编排的取消/重试输入;它们不能清 permit、点击意图、submission 或 result pending,也不能释放、重新授权、重新领取或推进下一任务。
11. T-401 前生产 composition/import/call graph 必须同时证明:不可达 SinglePassSubmissionOrchestrator/SubmitOrderPort;不存在生产 ReadyForSubmission factory;T-305 dry-run 结果类型不可流入提交编排。
## 验收要点
- 参数化负例逐一移除授权/首次许可、Gate2、Gate3 或唯一控件,证明 fake SubmitOrderPort 调用数均为 0。
- 首次明确 permit + durable 点击意图的纯测试用例证明 fake port 调用数恰好为 1;并发、二次进入、permit 重放和重复 result 均不能增加计数。
- fake port 在调用中或返回解析时抛错/超时,结果均为 reconcile-only/UNKNOWN,调用数仍恰好为 1 且无 retry。
- 崩溃点覆盖 fence 响应前后、permit 落盘前后、点击意图前后、fake port 调用前后和 result 上报前后;恢复全部零 SubmitOrderPort 调用。
- 固定测试至少包含 test_missing_each_submission_condition_never_calls_port、test_first_permit_calls_port_exactly_once、test_port_exception_never_retries、test_restart_after_click_intent_is_reconcile_only、test_t305_runtime_artifacts_are_not_accepted、test_no_production_ready_for_submission_factory、test_production_app_import_closure_cannot_reach_submission。
- AST/import/call-graph 证明生产代码不存在 ReadyForSubmission 构造入口,T-305 已终止结果/截图/缓存/proof 到编排无数据流,app/UI/PDD/device/真实 click/payment 不在本任务闭包。
- 任务 diff 只能修改窄 execution 实现/测试;T-308 submissions/remote/localstate/core 接口保持冻结且零 diff。
- client 全量 unittest、compileall、wheel metadata、完整 init、上下文、Vikunja export 与 diff-check 通过。
- needs_human_review:人工审阅 T-305 零运行时复用、生产 factory 不存在、一次性 fake port 调用和全部崩溃点;确认前不得标 DONE。
## 执行记录
(暂无)
<!-- END VIKUNJA EXPORT -->
## 边界
- T-305 正常 dry-run 已执行一次安全 Back,并以 `DRY_RUN_COMPLETED_BEFORE_FENCE` 终止 attempt。
本任务只能复用其已验收的代码/判据契约;不得接收或复用该次/任何 dry-run 的结果对象、attempt、
screenshot、AssetRef、action journal、缓存、页面对象、本地快照或旧 proof 来申请围栏或提交。
- 本任务只在单一窄 execution 文件实现纯 `SinglePassSubmissionOrchestrator`、只读
`ReadyForSubmission` 协议和抽象 `SubmitOrderPort`;仅测试 fixture 可实现/构造该协议。生产代码
不得存在具体 `ReadyForSubmission` 类、公共构造器、factory、adapter、反序列化或
`from_dry_run` / `from_snapshot` / `from_evidence` / `from_localstate` 路径。
- 本任务只消费 T-308 已冻结接口,不得修改 `submissions/**`、`remote/**`、`localstate/**` 或
`core/**`。不得修改或导入 `app.py`、`ui/**`、`pdd/**`、`device/**`,不得连接真机、使用
uiautomator2 或实现任何具体页面点击适配器。
- 测试 fixture 表达的四条件必须属于同一虚构 task/authorization/attempt/generation 和 App 版本:
未消费授权与首次明确围栏许可、Gate2、Gate3、精确唯一提交控件。它只验证纯逻辑,不是可复用的
页面或业务事实;缺失、漂移、金额/规格/数量冲突均零 fake `SubmitOrderPort` 调用。
- 编排必须先通过 T-308 durable 写入唯一点击意图,再把当前进程的一次性 `ArmedSubmission` 交给
fake `SubmitOrderPort.submit_once()`。该方法在纯测试生命周期最多调用一次;不得存在 retry、循环、
fallback、第二调用点或异常后重新进入路径。
- 点击意图落盘后即进入 `RECONCILE_ONLY`。fake port 返回、抛错、超时、停止、关闭或进程崩溃都不得
回到可提交状态;结果不明确只能形成 `UNKNOWN` 并调和同一 submission,不能再次调用 port、重新
申请许可、释放围栏、重新授权或重新领取。
- 本任务不实现真实页面结果判据。fake fixture/port 的 `SUBMITTED` 只用于协议分支测试,不能成为
生产页面事实;不得从 cmroubao/cmpdd、旧版本、页面语义或人工经验推断真实 `SUBMITTED`。
- T-401 前 production composition/import/call graph 必须同时不可达本任务编排/`SubmitOrderPort`,
不存在生产 `ReadyForSubmission` 构造入口,并证明 T-305 终止结果到提交编排无数据流。应用启动、
Esc、停止轮询、查看历史和关闭窗口均保持零提交调用。
- T-401 才能领取一条全新已授权真实任务,在新的同一趟 attempt 中重新执行全部实时闸门并即时构造
生产 `ReadyForSubmission`。本任务不提供人工提交入口,不编写支付、免密支付、先用后付或扣款能力。
- `needs_human_review: true`:自动验证后仍保持 `DOING`,等待人审 T-305 零运行时复用、生产 factory
不存在、一次性 fake port 调用和全部崩溃点;未经明确确认不得标 `DONE`。