diff --git a/client/scripts/capture_sku_reveal_spike.py b/client/scripts/capture_sku_reveal_spike.py index 2b40e90..f853b0e 100644 --- a/client/scripts/capture_sku_reveal_spike.py +++ b/client/scripts/capture_sku_reveal_spike.py @@ -15,7 +15,7 @@ from cmbuyer_client.device.adb import AdbClient, DeviceConnectionError, Subproce from cmbuyer_client.device.baseline import NoReconnectUiautomatorConnector from cmbuyer_client.pdd.sku_reveal_spike import SkuRevealSpikeCapturer, SkuRevealSpikeError from cmbuyer_client.pdd.sku_selection import EXPECTED_GOODS_ID, SkuSelectionError -from cmbuyer_client.pdd.sku_selection_runner import SkuSelectionRunError +from cmbuyer_client.pdd.sku_selection_runner import SkuSelectionRunError, safe_failure_stage def parse_arguments(argv: list[str] | None = None) -> argparse.Namespace: @@ -71,9 +71,12 @@ def main(argv: list[str] | None = None) -> int: arguments.goods_id, arguments.output_dir, ) - except (DeviceConnectionError, SkuSelectionError, SkuSelectionRunError, SkuRevealSpikeError): + except (DeviceConnectionError, SkuSelectionError, SkuSelectionRunError, SkuRevealSpikeError) as error: # 不回显页面正文、节点、serial、坐标、路径或第三方异常。 - print("规格 reveal 取证失败:已停止,未发布本地证据目录。", file=sys.stderr) + print( + f"规格 reveal 取证失败:stage={safe_failure_stage(error)};已停止,未发布本地证据目录。", + file=sys.stderr, + ) return 1 except OSError: print("规格 reveal 取证失败:无法创建或发布本地证据目录。", file=sys.stderr) diff --git a/client/tests/pdd/test_sku_reveal_spike.py b/client/tests/pdd/test_sku_reveal_spike.py index 85c6f88..99bc79f 100644 --- a/client/tests/pdd/test_sku_reveal_spike.py +++ b/client/tests/pdd/test_sku_reveal_spike.py @@ -20,7 +20,11 @@ from cmbuyer_client.pdd.sku_reveal_spike import ( SkuRevealSpikeError, _require_safe_reveal_path, ) -from cmbuyer_client.pdd.sku_selection import SkuSelectionError, _parse_nodes +from cmbuyer_client.pdd.sku_selection import ( + SkuSelectionError, + _annotate_sku_entry_failure, + _parse_nodes, +) _FIXTURES = Path(__file__).with_name("fixtures") @@ -442,10 +446,47 @@ class SkuRevealSpikeCliTests(unittest.TestCase): ) output = stderr.getvalue() self.assertEqual(status, 1) + self.assertIn("stage=unknown", output) self.assertNotIn("Traceback", output) self.assertNotIn("192.168.0.173:5555", output) self.assertNotIn("private", output) + def test_cli_reports_only_formally_annotated_entry_stage(self) -> None: + script = _load_reveal_script() + secret = "SERIAL=192.168.0.173:5555 private" + + def run_with(error: BaseException) -> str: + class FailingCapturer: + def __init__(self, *args: object, **kwargs: object) -> None: + return None + + def capture(self, *args: object, **kwargs: object) -> object: + raise error + + stderr = io.StringIO() + with patch.object(script, "SkuRevealSpikeCapturer", FailingCapturer), redirect_stderr(stderr): + status = script.main( + [ + "--serial", "192.168.0.173:5555", + "--goods-id", "937122477375", + "--output-dir", "evidence", + ] + ) + self.assertEqual(status, 1) + output = stderr.getvalue() + self.assertNotIn("Traceback", output) + self.assertNotIn("192.168.0.173:5555", output) + self.assertNotIn("private", output) + return output + + annotated = SkuSelectionError(secret) + _annotate_sku_entry_failure(annotated, "sku_entry_panel_verify") + self.assertIn("stage=sku_entry_panel_verify", run_with(annotated)) + + spoofed = SkuSelectionError(secret) + setattr(spoofed, "_cmbuyer_failure_stage", "sku_entry_panel_verify") + self.assertIn("stage=unknown", run_with(spoofed)) + def _load_reveal_script() -> object: path = Path(__file__).resolve().parents[2] / "scripts" / "capture_sku_reveal_spike.py" diff --git a/docs/tasks/T-103.md b/docs/tasks/T-103.md index 764ed2b..18b61da 100644 --- a/docs/tasks/T-103.md +++ b/docs/tasks/T-103.md @@ -25,7 +25,7 @@ write_paths: - docs/current-state.md --- - + ## 问题 / 背景 T-102 已证明 canonical 链接可进入目标商品。T-103 在 PKG110 / Android 16 / 拼多多 8.17.0、goods_id `937122477375` 上确认:详情页底部购买区第一行“快要抢光 + 金额”是唯一获准的受控规格入口;商品内容区同名小字和第二行“免拼购买”都不得点击。 @@ -282,6 +282,10 @@ T-103 sanitizer v2 坐标修正与主审:提交 44c027a 将 screenshot space ### 2026-08-05T04:56:30Z · ila 2026-08-05 T-103 reveal 放行前独立审计返修:实现未发现生产缺陷,但原测试未永久覆盖验收条目中的四类后置失败。仅修改 client/tests/pdd/test_sku_reveal_spike.py,新增端到端反例:swipe 后页面不变/尺码仍隐藏、目标颜色 selected 漂移、提交硬拒绝区文本缺失/重复/上移、RPC 结果不明且页面不变。每例均断言 swipe 恰好一次且不重试、target/manifest 不发布、staging 无残留。受托 agent 聚焦 59/59 PASS;主 agent 独立逐行审查并复跑 client 全量 259/259(13.883s)、compileall、agent-context、diff-check 全部 PASS。未改生产代码,未引入数量、确认页、围栏、提交或支付能力。T-103 继续保持 Doing;可在该修复提交推送后放行项目所有者执行唯一一次真机 reveal。 + +### 2026-08-05T06:19:03Z · ila + +2026-08-05 T-103 第二次真机失败诊断:项目所有者确认从拼多多首页运行后已进入目标商品并弹出规格面板,但面板保留此前人工选择的“白色 + 其他尺码”,脚本没有选择“黑色CHA(纯棉)”或目标尺码。按调用链,入口后置只接受 PANEL_OPEN_EMPTY;非目标规格使 `sku_entry_panel_verify` 失败,尚未进入 `select_sku_options`,因此 zero target-color click / zero reveal swipe / zero submit。为避免统一错误再次丢失控制流事实,仅修改 capture_sku_reveal_spike.py 复用既有 safe_failure_stage,输出白名单 stage 或 unknown;异常正文、页面文本、serial、XML、路径仍不回显,伪造 stage 无正式 marker 时降级 unknown。独立审计与主审均确认未改 Capturer/Flow/判据/动作;聚焦 60/60、client 全量 260/260(14.788s)、compileall、agent-context、diff-check PASS。T-103 继续 Doing,下一步先由人把该商品规格面板恢复为颜色/尺码均未选,再从首页执行全新 output-dir。 ## 边界