Compare commits
2
Commits
1d60a91ace
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db7b61b90b | ||
|
|
50a572f3e3 |
@@ -31,15 +31,6 @@ FINAL_PANEL_QUANTITY = 2
|
||||
_PDD_ID = "com.xunmeng.pinduoduo:id/pdd"
|
||||
_QUANTITY_ID = "com.xunmeng.pinduoduo:id/gnl"
|
||||
_TITLE_ID = "com.xunmeng.pinduoduo:id/tv_title"
|
||||
_PANEL_BOUNDS = "[0,551][1080,938]"
|
||||
_PRICE_FRAME_BOUNDS = "[396,575][1053,647]"
|
||||
_PRICE_ROW_BOUNDS = "[396,575][740,647]"
|
||||
_PRICE_TEXT_BOUNDS = "[396,580][722,647]"
|
||||
_SUMMARY_BOUNDS = "[396,659][1053,721]"
|
||||
_QUANTITY_BOUNDS = "[396,827][645,902]"
|
||||
_MINUS_BOUNDS = "[396,827][474,902]"
|
||||
_VALUE_BOUNDS = "[480,827][561,902]"
|
||||
_PLUS_BOUNDS = "[567,827][645,902]"
|
||||
_SUBMIT_TEXT_BOUNDS = "[366,2225][714,2284]"
|
||||
_SUBMIT_TEXT_PARENT_BOUNDS = "[354,2181][726,2328]"
|
||||
_SUBMIT_ACTION_BOUNDS = "[0,2181][1080,2328]"
|
||||
@@ -63,6 +54,50 @@ _ENTRY_CHILDREN = (
|
||||
_DANGEROUS_RETURN_TERMS = ("提交订单", "确认订单", "立即支付", "去支付", "付款")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _PanelProfile:
|
||||
evidence_id: str
|
||||
panel_bounds: str
|
||||
price_frame_bounds: str
|
||||
price_row_bounds: str
|
||||
price_text_bounds: str
|
||||
summary_bounds: str
|
||||
quantity_bounds: str
|
||||
minus_bounds: str
|
||||
value_bounds: str
|
||||
plus_bounds: str
|
||||
|
||||
|
||||
# 两个配置分别绑定 T-106 原始证据和 T-107 同版本人工五项确认的新证据。
|
||||
# 只能整套精确命中其中一个,禁止把 77px 位移改成范围或混搭坐标。
|
||||
_PANEL_PROFILES = (
|
||||
_PanelProfile(
|
||||
evidence_id="t106-final-panel-8.17.0",
|
||||
panel_bounds="[0,551][1080,938]",
|
||||
price_frame_bounds="[396,575][1053,647]",
|
||||
price_row_bounds="[396,575][740,647]",
|
||||
price_text_bounds="[396,580][722,647]",
|
||||
summary_bounds="[396,659][1053,721]",
|
||||
quantity_bounds="[396,827][645,902]",
|
||||
minus_bounds="[396,827][474,902]",
|
||||
value_bounds="[480,827][561,902]",
|
||||
plus_bounds="[567,827][645,902]",
|
||||
),
|
||||
_PanelProfile(
|
||||
evidence_id="t107-final-panel-shifted-8.17.0",
|
||||
panel_bounds="[0,474][1080,861]",
|
||||
price_frame_bounds="[396,498][1053,570]",
|
||||
price_row_bounds="[396,498][740,570]",
|
||||
price_text_bounds="[396,503][722,570]",
|
||||
summary_bounds="[396,582][1053,644]",
|
||||
quantity_bounds="[396,750][645,825]",
|
||||
minus_bounds="[396,750][474,825]",
|
||||
value_bounds="[480,750][561,825]",
|
||||
plus_bounds="[567,750][645,825]",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class FinalSubmitPanelError(RuntimeError):
|
||||
"""最终面板或 Gate3 判据不成立时的脱敏安全停止。"""
|
||||
|
||||
@@ -254,28 +289,32 @@ def _validate_gate2(gate2: object) -> None:
|
||||
|
||||
|
||||
def _verified_final_panel(nodes: list[_Node]) -> _VerifiedPanel:
|
||||
panel = _one(
|
||||
node
|
||||
profile_matches = [
|
||||
(profile, node)
|
||||
for profile in _PANEL_PROFILES
|
||||
for node in nodes
|
||||
if _exact(node, "android.view.ViewGroup", _PANEL_BOUNDS, resource_id=_PDD_ID)
|
||||
)
|
||||
if _exact(node, "android.view.ViewGroup", profile.panel_bounds, resource_id=_PDD_ID)
|
||||
]
|
||||
if len(profile_matches) != 1:
|
||||
raise FinalSubmitPanelError("最终面板未唯一命中已人工确认的精确布局配置。")
|
||||
profile, panel = profile_matches[0]
|
||||
price_frame = _one(
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is panel
|
||||
and _exact(node, "android.widget.FrameLayout", _PRICE_FRAME_BOUNDS, resource_id=_PDD_ID)
|
||||
and _exact(node, "android.widget.FrameLayout", profile.price_frame_bounds, resource_id=_PDD_ID)
|
||||
)
|
||||
price_row = _one(
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is price_frame
|
||||
and _exact(node, "android.widget.LinearLayout", _PRICE_ROW_BOUNDS, resource_id=_PDD_ID)
|
||||
and _exact(node, "android.widget.LinearLayout", profile.price_row_bounds, resource_id=_PDD_ID)
|
||||
)
|
||||
price = _one(
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is price_row
|
||||
and _exact_text_node(node, "android.widget.TextView", _PRICE_TEXT_BOUNDS, resource_id=_PDD_ID)
|
||||
and _exact_text_node(node, "android.widget.TextView", profile.price_text_bounds, resource_id=_PDD_ID)
|
||||
and _GATE2_TEXT.fullmatch(node.text) is not None
|
||||
)
|
||||
gate2_match = _GATE2_TEXT.fullmatch(price.text)
|
||||
@@ -287,20 +326,20 @@ def _verified_final_panel(nodes: list[_Node]) -> _VerifiedPanel:
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is panel
|
||||
and _exact_text_node(node, "android.widget.TextView", _SUMMARY_BOUNDS, resource_id=_PDD_ID)
|
||||
and _exact_text_node(node, "android.widget.TextView", profile.summary_bounds, resource_id=_PDD_ID)
|
||||
and node.text == _EXPECTED_SUMMARY
|
||||
)
|
||||
quantity_outer = _one(
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is panel
|
||||
and _exact(node, "android.widget.LinearLayout", _QUANTITY_BOUNDS, resource_id=_QUANTITY_ID)
|
||||
and _exact(node, "android.widget.LinearLayout", profile.quantity_bounds, resource_id=_QUANTITY_ID)
|
||||
)
|
||||
quantity_inner = _one(
|
||||
node
|
||||
for node in nodes
|
||||
if node.parent is quantity_outer
|
||||
and _exact(node, "android.widget.LinearLayout", _QUANTITY_BOUNDS, resource_id="")
|
||||
and _exact(node, "android.widget.LinearLayout", profile.quantity_bounds, resource_id="")
|
||||
)
|
||||
quantity_children = _direct_children(quantity_inner)
|
||||
if len(quantity_children) != 3:
|
||||
@@ -308,21 +347,21 @@ def _verified_final_panel(nodes: list[_Node]) -> _VerifiedPanel:
|
||||
_one(
|
||||
node
|
||||
for node in quantity_children
|
||||
if _exact(node, "android.widget.ImageView", _MINUS_BOUNDS, resource_id=_PDD_ID, clickable="true")
|
||||
if _exact(node, "android.widget.ImageView", profile.minus_bounds, resource_id=_PDD_ID, clickable="true")
|
||||
and not node.text
|
||||
and node.desc == "减少数量"
|
||||
)
|
||||
_one(
|
||||
node
|
||||
for node in quantity_children
|
||||
if _exact(node, "android.widget.EditText", _VALUE_BOUNDS, resource_id=_PDD_ID, clickable="true")
|
||||
if _exact(node, "android.widget.EditText", profile.value_bounds, resource_id=_PDD_ID, clickable="true")
|
||||
and node.text == str(FINAL_PANEL_QUANTITY)
|
||||
and not node.desc
|
||||
)
|
||||
_one(
|
||||
node
|
||||
for node in quantity_children
|
||||
if _exact(node, "android.widget.ImageView", _PLUS_BOUNDS, resource_id=_PDD_ID, clickable="true")
|
||||
if _exact(node, "android.widget.ImageView", profile.plus_bounds, resource_id=_PDD_ID, clickable="true")
|
||||
and not node.text
|
||||
and node.desc == "增加数量"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<hierarchy rotation="0">
|
||||
<!-- T-107 2026-08-06 人工五项确认:同版本面板整体上移 77px;底部最终控件不变。 -->
|
||||
<node package="com.xunmeng.pinduoduo" class="android.view.ViewGroup" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[0,474][1080,861]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.FrameLayout" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[396,498][1053,570]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.LinearLayout" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[396,498][740,570]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node text="快卖完 ¥32.76" package="com.xunmeng.pinduoduo" class="android.widget.TextView" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[396,503][722,570]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
</node>
|
||||
</node>
|
||||
<node text="已选: 黑色 CHA (纯棉) M(建议100-115)" package="com.xunmeng.pinduoduo" class="android.widget.TextView" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[396,582][1053,644]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.LinearLayout" resource-id="com.xunmeng.pinduoduo:id/gnl" bounds="[396,750][645,825]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.LinearLayout" bounds="[396,750][645,825]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node content-desc="减少数量" package="com.xunmeng.pinduoduo" class="android.widget.ImageView" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[396,750][474,825]" clickable="true" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
<node text="2" package="com.xunmeng.pinduoduo" class="android.widget.EditText" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[480,750][561,825]" clickable="true" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
<node content-desc="增加数量" package="com.xunmeng.pinduoduo" class="android.widget.ImageView" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[567,750][645,825]" clickable="true" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node package="com.xunmeng.pinduoduo" class="android.view.ViewGroup" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[0,366][1080,2328]" clickable="true" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.FrameLayout" resource-id="com.xunmeng.pinduoduo:id/pdd" bounds="[0,2181][1080,2328]" clickable="true" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node package="com.xunmeng.pinduoduo" class="android.widget.LinearLayout" bounds="[354,2181][726,2328]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false">
|
||||
<node text="提交订单 ¥32.76" package="com.xunmeng.pinduoduo" class="android.widget.TextView" bounds="[366,2225][714,2284]" clickable="false" enabled="true" visible-to-user="true" selected="false" scrollable="false" />
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
@@ -52,8 +52,10 @@ from cmbuyer_client.pdd.quantity_gate2 import (
|
||||
SERIAL = "192.168.0.173:5555"
|
||||
FIXTURES = Path(__file__).with_name("fixtures")
|
||||
PANEL_FIXTURE = FIXTURES / "final_submit_panel_8_17_0.xml"
|
||||
SHIFTED_PANEL_FIXTURE = FIXTURES / "final_submit_panel_shifted_8_17_0.xml"
|
||||
EXIT_FIXTURE = FIXTURES / "final_submit_panel_exit_8_17_0.xml"
|
||||
PANEL = PANEL_FIXTURE.read_text(encoding="utf-8")
|
||||
SHIFTED_PANEL = SHIFTED_PANEL_FIXTURE.read_text(encoding="utf-8")
|
||||
EXIT = EXIT_FIXTURE.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@@ -119,6 +121,23 @@ class FinalSubmitPanelObserverTests(unittest.TestCase):
|
||||
self.assertTrue(observation.nearest_clickable_ancestor_unique)
|
||||
self.assertEqual(observation.quantity_read, 2)
|
||||
|
||||
def test_each_human_confirmed_exact_panel_profile_is_accepted_independently(self) -> None:
|
||||
for raw in (PANEL, SHIFTED_PANEL):
|
||||
with self.subTest(panel_bounds=ElementTree.fromstring(raw)[0].get("bounds")):
|
||||
observation = _observe(raw)
|
||||
self.assertEqual(observation.gate2_panel_total_price, "32.76")
|
||||
self.assertEqual(observation.gate3_submit_amount, "32.76")
|
||||
|
||||
def test_panel_profiles_cannot_be_ranged_or_mixed(self) -> None:
|
||||
cases = (
|
||||
SHIFTED_PANEL.replace("[0,474][1080,861]", "[0,475][1080,862]"),
|
||||
SHIFTED_PANEL.replace("[396,582][1053,644]", "[396,659][1053,721]"),
|
||||
PANEL.replace("[396,827][645,902]", "[396,750][645,825]"),
|
||||
)
|
||||
for raw in cases:
|
||||
with self.subTest(), self.assertRaises(FinalSubmitPanelError):
|
||||
_observe(raw)
|
||||
|
||||
def test_dynamic_canonical_amount_is_allowed_only_when_both_roles_and_gate2_agree(self) -> None:
|
||||
raw = PANEL.replace("快卖完 ¥32.76", "快卖完 ¥39.99").replace(
|
||||
"提交订单 ¥32.76", "提交订单 ¥39.99"
|
||||
@@ -675,7 +694,10 @@ class FinalSubmitPanelStaticBoundaryTests(unittest.TestCase):
|
||||
self.assertNotIn(token, source)
|
||||
|
||||
def test_fixtures_contain_no_unrelated_personal_or_payment_data(self) -> None:
|
||||
combined = PANEL_FIXTURE.read_text(encoding="utf-8") + EXIT_FIXTURE.read_text(encoding="utf-8")
|
||||
combined = "".join(
|
||||
path.read_text(encoding="utf-8")
|
||||
for path in (PANEL_FIXTURE, SHIFTED_PANEL_FIXTURE, EXIT_FIXTURE)
|
||||
)
|
||||
for forbidden in ("手机号", "收货地址", "支付密码", "微信支付", "支付宝"):
|
||||
self.assertNotIn(forbidden, combined)
|
||||
|
||||
|
||||
+2
-1
@@ -464,7 +464,8 @@ T-103 只实现隔离的 `SkuSelectionFlow`:前四项加安全退出。它的
|
||||
T-107 已实现的 `observe_gate3()` 是无 device 的纯 XML observer,只接受可信 `Gate2Observation`、当前
|
||||
XML、显式截图路径和带时区采集时间。它分别重读当前面板顶部 `gate2_panel_total_price` 与最终完整文本
|
||||
`提交订单 ¥{gate3_submit_amount}`,要求两者唯一、规范、严格相等且不超 `max_total_price`;同时精确
|
||||
复核目标规格摘要和数量 2。成功 DTO 只包含规格/数量、两个独立金额、完整文本、匹配数、启用态、
|
||||
复核目标规格摘要和数量 2。面板坐标只接受 T-106 原态或 T-107 人工五项确认的整体上移 77px 态,
|
||||
两套配置互斥且所有角色必须整套精确命中,不接受范围匹配或坐标混搭。成功 DTO 只包含规格/数量、两个独立金额、完整文本、匹配数、启用态、
|
||||
“最近可点击祖先唯一”布尔值、截图路径和采集时间,不包含 selector、bounds、XML 节点、祖先句柄或
|
||||
可操作对象。`FinalSubmitPanelRunner` 永久是围栏前 dry-run:RPC 仅允许截图、XML 和一次 `pressKey Back`,
|
||||
不接受提交开关,不上传证据;Back 结果不明或返回页未连续两帧命中 T-106 同商品正判据时不重试。
|
||||
|
||||
+11
-6
@@ -12,7 +12,7 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-08-06
|
||||
- 阶段:**Phase 1 · T-107 合并式最终提交面板 Gate3 判据与 Phase 2 安全服务端任务并行**
|
||||
- 阶段:**Phase 1 · T-107 已完成;Phase 2 安全服务端任务继续并行**
|
||||
- MVP 形态:手工填链接建单 → 批量“开始采购”并签发一次性授权 → 定时领取 → 同一趟精确
|
||||
选规格 / 三道价格闸门 / 提交围栏 / 点击一次 → 待付款。管理员点击“开始采购”即授权桌面端
|
||||
创建待付款订单;不再有试选后确认,系统绝不自动付款。
|
||||
@@ -66,11 +66,11 @@
|
||||
| 路径 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `docs/` | 已有 | 项目规范化文档,本次已完整生成 |
|
||||
| `docs/tasks/` | 已有(含 T-001~T-111、T-201~T-211、T-301~T-308、T-400~T-405) | T-103~T-106、T-303、T-211 已完成;T-304 代码完成后保持 DOING 等人工 UI 复核;T-107 将从两态真机证据固化合并式最终面板 Gate3;双端围栏、真实提交、调和、打包与只读验收均已落成任务 |
|
||||
| `docs/tasks/` | 已有(含 T-001~T-111、T-201~T-211、T-301~T-308、T-400~T-405) | T-103~T-107、T-303、T-211 已完成;T-304 代码完成后保持 DOING 等人工 UI 复核;双端围栏、真实提交、调和、打包与只读验收均已落成任务 |
|
||||
| `docs/design/` | 已有(6 个原型) | web 登录 / 建单 / 工作台 / 详情,desk 采购执行 / 配置;均已人工确认 |
|
||||
| `scripts/` | 已有 | 上下文门禁、Vikunja 单向导出与 MCP 启动包装 |
|
||||
| `admin/` | 已初始化 | Go 1.23+ / gin / SQLite,含建单/授权/详情/证据/设备身份与原子 claim/renew;不执行真机动作 |
|
||||
| `client/` | 已初始化 | Python 3.11+、PySide6/uiautomator2、固定双 Tab 主界面、安全轮询、严格 HTTP/DPAPI/SQLite 恢复底座及受控规格选择/读价/数量 Gate2/安全退出;T-106 两态取证已完成,T-107 Gate3 纯 observer 与一次 Back runner 已实现并等待真机人工验收,最终提交仍未开放 |
|
||||
| `client/` | 已初始化 | Python 3.11+、PySide6/uiautomator2、固定双 Tab 主界面、安全轮询、严格 HTTP/DPAPI/SQLite 恢复底座及受控规格选择/读价/数量 Gate2/安全退出;T-107 Gate3 纯 observer 与一次 Back runner 已完成真机人工验收,最终提交仍未开放 |
|
||||
| `init.ps1` / `init.sh` | 已完成 | 统一安装与离线验证入口;PowerShell 优先复用合规 venv,缺失时自动选择最高的 Python 3.11+,Unix 缺工具链明确失败 |
|
||||
|
||||
## 任务状态
|
||||
@@ -90,7 +90,7 @@
|
||||
稳定重放和续租;claim token 不是提交许可,尚无事件、提交围栏或结果 API。
|
||||
- T-103 已完成受控入口、精确颜色、固定 reveal、exact M、规格面板 `12.88` 读价、一次 Back 和最终
|
||||
人工真机验收;T-104 已完成同商品安全退出,T-105 已完成数量/Gate2,T-106 已完成合并式最终面板
|
||||
与一次 Back 返回两态只读取证,T-107 将据此固化 Gate3 判据。真机运行要求
|
||||
与一次 Back 返回两态只读取证,T-107 已据此固化并真机验收 Gate3 判据。真机运行要求
|
||||
同一物理手机只保留一个 ADB 通道;CLI 已增加不回显页面
|
||||
正文的固定白名单失败阶段码;`sku_entry` 动态页面误判已按最新真机证据修复,覆盖点击中心的浮窗
|
||||
会保持零点击,入口失败也已细分为前置、发现、点击和面板后置验证四个固定子阶段。服务端后续顺序已收紧为
|
||||
@@ -255,6 +255,10 @@ Back、未点击提交订单、未创建待付款订单、未进入付款/安全
|
||||
T-107 代码已从上述两态证据提取最小 fixture:纯 XML observer 分别复核顶部 Gate2、规格摘要、数量 2、
|
||||
最终完整文本 Gate3 及最近可点击祖先;DTO 不暴露节点/坐标/selector。runner 在观察成功前零页面动作,
|
||||
成功后只发送一次 Android Back,并要求返回页连续两帧命中完整商品标题和底部入口正锚;结果不明不重试。
|
||||
2026-08-06 首次真机 dry-run 先因 Wi-Fi ADB 断开停止;恢复连接后发现目标五项均正确,但面板受当前内容
|
||||
高度影响整体上移 77px。新增截图/XML 在
|
||||
`%LOCALAPPDATA%\cmbuyer\artifacts\T-107\gate3-layout-shift-937122477375-20260806\raw`,项目所有者已人工确认
|
||||
颜色、尺码、数量和两处金额。实现仅把该状态加入第二套互斥精确配置,不放宽坐标范围,也不允许跨配置混搭。
|
||||
真机人工准备同一最终面板后,从仓库根运行:
|
||||
|
||||
```powershell
|
||||
@@ -262,8 +266,9 @@ T-107 代码已从上述两态证据提取最小 fixture:纯 XML observer 分
|
||||
```
|
||||
|
||||
该命令会重新核对当前拼多多版本、前台应用、设备和屏幕,不会点击红色“提交订单”,也不会申请围栏、
|
||||
创建待付款订单或进入付款。T-107 保持 `DOING`,等待项目所有者确认 Gate2/Gate3 均为 `32.76`、
|
||||
完整文本 match count=1、最近可点击祖先唯一、一次 Back 安全,且未创建订单/进入付款。
|
||||
创建待付款订单或进入付款。2026-08-06 真机结果发布于同名输出目录:Gate2/Gate3 均为 `32.76`、
|
||||
完整文本 match count=1、最近可点击祖先唯一、页面控件动作 0、一次 Back 安全返回。项目所有者最终确认
|
||||
规格面板已关闭,未创建待付款订单,未进入付款或安全验证页面;T-107 已完成。
|
||||
|
||||
## 关键背景
|
||||
|
||||
|
||||
+15
-4
@@ -3,7 +3,7 @@ id: T-107
|
||||
title: 固化合并式最终提交面板闸门三与提交控件只读判据
|
||||
phase: 1
|
||||
deps: [T-106]
|
||||
status: DOING
|
||||
status: DONE
|
||||
created: 2026-08-04
|
||||
vikunja_task_id: 42
|
||||
context_ref: dbf9b8c
|
||||
@@ -16,13 +16,14 @@ write_paths:
|
||||
- client/src/cmbuyer_client/pdd/final_submit_panel_runner.py
|
||||
- client/tests/pdd/test_final_submit_panel.py
|
||||
- client/tests/pdd/fixtures/final_submit_panel_8_17_0.xml
|
||||
- client/tests/pdd/fixtures/final_submit_panel_shifted_8_17_0.xml
|
||||
- client/tests/pdd/fixtures/final_submit_panel_exit_8_17_0.xml
|
||||
- client/scripts/run_t107_final_submit_panel_dry_run.py
|
||||
- docs/api.md
|
||||
- docs/current-state.md
|
||||
---
|
||||
|
||||
<!-- BEGIN VIKUNJA EXPORT id=42 synced=2026-08-06T04:08:33Z sha256=abf49f164d13d9ff9707ef41e4e55b11a4e267d7accfe6f8c177e0bf92373309 -->
|
||||
<!-- BEGIN VIKUNJA EXPORT id=42 synced=2026-08-06T07:36:32Z sha256=0dc3e6197aaad376f4b2fa344a78d255caabeb82eda17e56f8b9643fad164ff4 -->
|
||||
## 问题 / 背景
|
||||
|
||||
T-106 真机证据证明当前拼多多 8.17.0 使用合并式最终提交面板:没有独立确认页导航;面板顶部是 Gate2 总额,红色“提交订单 ¥金额”会直接创建待付款订单。T-107 只固化该稳定面板的 Gate3 只读金额、最终控件观察与一次安全返回;绝不点击最终提交。
|
||||
@@ -68,13 +69,23 @@ T-106 真机证据证明当前拼多多 8.17.0 使用合并式最终提交面板
|
||||
### 2026-08-06T04:08:25Z · ila
|
||||
|
||||
2026-08-06 T-107 实现候选完成:基于 T-106 人工确认的拼多多 8.17.0 / goods_id 937122477375 两态原始 XML,实现纯 XML Gate3 observer、零页面控件动作 dry-run runner、成功后唯一一次 Android Back、连续两帧同商品返回正判据及本地原子证据。最终提交控件只读,未引入 fence/permit/submit/payment 能力。fixture/静态边界/金额/结构/一次返回等 36 项专项测试通过;client 全量 382 项 unittest、compileall、admin go test/vet/build、完整 init.ps1、agent-context 校验均通过;两份 T-106 原始 XML 离线复核通过。任务因 needs_device/needs_human_review 保持 DOING,等待项目所有者按任务命令确认 Gate2=Gate3=32.76、match_count=1、最近可点击祖先唯一、仅一次 Back,且未创建订单/进入付款。
|
||||
|
||||
### 2026-08-06T07:32:24Z · ila
|
||||
|
||||
2026-08-06 T-107 真机修复与运行记录:首次 dry-run 的首个真实错误为 Wi-Fi ADB 未连接,恢复后只读发现已选规格曾为“黑色 连线星星(纯棉)”,人工改回目标后,五项(黑色 CHA(纯棉)、M、数量2、顶部32.76、底部提交订单32.76)由项目所有者确认。新只读取证保存于 C:\Users\ila20\AppData\Local\cmbuyer\artifacts\T-107\gate3-layout-shift-937122477375-20260806\raw,PDD 8.17.0、1080x2376,0 页面动作/0 Back/0 submit;该态相对 T-106 面板整体上移77px。实现增加第二套互斥完整精确配置,禁止范围匹配和跨配置混搭。38项专项测试、client全量384项、compileall、admin test/vet/build、完整init.ps1、上下文校验通过;T-106原XML与新XML均离线通过。随后唯一一次真机dry-run成功,输出 C:\Users\ila20\AppData\Local\cmbuyer\artifacts\T-107\final-submit-panel-dry-run-937122477375;manifest:Gate2=Gate3=32.76、match_count=1、enabled=true、nearest_clickable_ancestor_unique=true、page_control_action_attempts=0、back_attempts=1、safe_exit=completed。保持DOING,等待项目所有者最终确认手机未创建订单、未进入付款/安全验证页面。
|
||||
|
||||
### 2026-08-06T07:35:47Z · ila
|
||||
|
||||
2026-08-06 项目所有者最终三项确认:手机已返回商品详情页且规格面板关闭;未创建待付款订单;未进入付款或安全验证页面。结合 manifest 的 Gate2=Gate3=32.76、match_count=1、最近可点击祖先唯一、页面控件动作0、Back恰好1次,以及完整门禁通过,T-107 全部验收要点满足,批准完成。
|
||||
<!-- END VIKUNJA EXPORT -->
|
||||
|
||||
## 边界
|
||||
|
||||
- 合并式最终提交面板、Gate2 顶部金额、Gate3 最终控件金额、控件结构和返回后置条件只能来自 T-106
|
||||
已由人确认的真机证据,并与拼多多 8.17.0/goods_id 绑定。不得从前序项目、旧 XML、Activity 名、
|
||||
相似文案、OCR、裸坐标或推理补判据。
|
||||
已由人确认的真机证据,以及 2026-08-06 T-107 人工五项确认的同版本精确布局证据
|
||||
`C:\Users\ila20\AppData\Local\cmbuyer\artifacts\T-107\gate3-layout-shift-937122477375-20260806\raw`;
|
||||
两者都必须与拼多多 8.17.0/goods_id 绑定并作为互斥完整配置精确命中。不得从前序项目、旧 XML、
|
||||
Activity 名、相似文案、OCR、坐标范围或推理补判据,也不得混搭两组坐标。
|
||||
- 不存在也不得重新引入 `go_to_order_confirm()` 或任何确认页导航点击。T-107 成功路径对页面控件的
|
||||
点击数必须为 0;只有观察完成后的一次 Android Back。
|
||||
- Gate2 仍只从面板顶部已批准角色读取目标数量总额;Gate3 只从最终控件的结构化精确文本
|
||||
|
||||
Reference in New Issue
Block a user