100 lines
10 KiB
Markdown
100 lines
10 KiB
Markdown
---
|
||
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-06T02:59:09Z sha256=06eb0eef25f8c77b7b3a97f1fc0d0b018aa2e3c912ed329aa18fef9c57ce125f -->
|
||
## 问题 / 背景
|
||
|
||
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。
|
||
|
||
## 执行记录
|
||
|
||
### 2026-08-06T02:58:55Z · ila
|
||
|
||
2026-08-06T02:58:54.857Z · Codex
|
||
2026-08-06 离线 fixture/编排中的 Gate3 字段改为 gate3_submit_amount,提交控件必须是唯一结构化完整文本“提交订单 ¥{金额}”且最近可点击祖先唯一。Gate3 必须严格等于 Gate2;不再表达独立确认页。
|
||
<!-- END VIKUNJA EXPORT -->
|
||
|
||
## 边界
|
||
|
||
- 2026-08-06 起离线事实使用 `gate3_submit_amount` 和结构化完整控件文本,不再使用确认页金额语义。
|
||
Gate3 必须严格等于 Gate2,最近可点击祖先必须唯一;fixture 不表达独立确认页或导航动作。
|
||
|
||
- 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`。
|