Compare commits
2
Commits
cf3692112c
...
946b064470
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
946b064470 | ||
|
|
44c027a18f |
@@ -21,13 +21,15 @@ from ..pdd.product_url import ProductUrl, ProductUrlError, parse_product_url
|
||||
from ..pdd.sku_panel_state import HUMAN_DECLARED_STATES
|
||||
|
||||
|
||||
SANITIZER_VERSION = "t103-privacy-v1"
|
||||
SANITIZER_VERSION = "t103-privacy-v2"
|
||||
EXPECTED_GOODS_ID = "937122477375"
|
||||
EXPECTED_PDD_VERSION = "8.17.0"
|
||||
EXPECTED_DEVICE_MODEL = "PKG110"
|
||||
EXPECTED_ANDROID_VERSION = "16"
|
||||
EXPECTED_WIDTH = 1080
|
||||
EXPECTED_HEIGHT = 2400
|
||||
EXPECTED_SCREENSHOT_WIDTH = 1080
|
||||
EXPECTED_SCREENSHOT_HEIGHT = 2376
|
||||
EXPECTED_XML_WIDTH = 1080
|
||||
EXPECTED_XML_HEIGHT = 2400
|
||||
_ARTIFACT_FILES = ("screenshot.png", "hierarchy.xml")
|
||||
_SHA256_RE = re.compile(r"[0-9a-f]{64}\Z")
|
||||
_BOUNDS_RE = re.compile(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]\Z")
|
||||
@@ -48,6 +50,8 @@ class _CleanupStats:
|
||||
removed_nodes: int = 0
|
||||
cleared_crossing_nodes: int = 0
|
||||
retained_below_nodes: int = 0
|
||||
max_right: int = 0
|
||||
max_bottom: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -55,15 +59,19 @@ class PrivacyMaskConfig:
|
||||
"""仅描述已人工确认的隐私几何区域,绝不承担页面或规格判据。"""
|
||||
|
||||
version: str
|
||||
width: int
|
||||
height: int
|
||||
screenshot_width: int
|
||||
screenshot_height: int
|
||||
xml_width: int
|
||||
xml_height: int
|
||||
privacy_top: int
|
||||
|
||||
|
||||
PRIVACY_MASK_CONFIG = PrivacyMaskConfig(
|
||||
version=SANITIZER_VERSION,
|
||||
width=EXPECTED_WIDTH,
|
||||
height=EXPECTED_HEIGHT,
|
||||
screenshot_width=EXPECTED_SCREENSHOT_WIDTH,
|
||||
screenshot_height=EXPECTED_SCREENSHOT_HEIGHT,
|
||||
xml_width=EXPECTED_XML_WIDTH,
|
||||
xml_height=EXPECTED_XML_HEIGHT,
|
||||
# 主审在原始截图确认 y < 540 为收货/手机号区域;整宽遮罩优先保护隐私而非保留版面。
|
||||
privacy_top=540,
|
||||
)
|
||||
@@ -228,7 +236,10 @@ def _sanitize_screenshot(source: Path, target: Path) -> None:
|
||||
try:
|
||||
with Image.open(source) as image:
|
||||
image.load()
|
||||
if image.format != "PNG" or image.size != (PRIVACY_MASK_CONFIG.width, PRIVACY_MASK_CONFIG.height):
|
||||
if image.format != "PNG" or image.size != (
|
||||
PRIVACY_MASK_CONFIG.screenshot_width,
|
||||
PRIVACY_MASK_CONFIG.screenshot_height,
|
||||
):
|
||||
raise SkuEvidenceSanitizationError("原始截图分辨率或格式与脱敏配置不匹配。")
|
||||
sanitized = image.convert("RGBA")
|
||||
except SkuEvidenceSanitizationError:
|
||||
@@ -238,7 +249,7 @@ def _sanitize_screenshot(source: Path, target: Path) -> None:
|
||||
|
||||
# 用不透明黑色覆盖 y < 540,保证截图与 XML 使用相同的隐私几何边界。
|
||||
ImageDraw.Draw(sanitized).rectangle(
|
||||
(0, 0, PRIVACY_MASK_CONFIG.width - 1, PRIVACY_MASK_CONFIG.privacy_top - 1),
|
||||
(0, 0, PRIVACY_MASK_CONFIG.screenshot_width - 1, PRIVACY_MASK_CONFIG.privacy_top - 1),
|
||||
fill=(0, 0, 0, 255),
|
||||
)
|
||||
sanitized.save(target, format="PNG", optimize=False, compress_level=9)
|
||||
@@ -257,6 +268,7 @@ def _sanitize_hierarchy(source: Path, target: Path) -> _CleanupStats:
|
||||
_clear_node_text(root)
|
||||
for child in list(root):
|
||||
_sanitize_node(root, child, stats)
|
||||
_require_expected_xml_coordinate_space(stats)
|
||||
if stats.removed_nodes < 1 or stats.retained_below_nodes < 1:
|
||||
raise SkuEvidenceSanitizationError("原始节点树未满足隐私几何结构。")
|
||||
if _contains_phone(root):
|
||||
@@ -269,11 +281,15 @@ def _sanitize_node(parent: ElementTree.Element, node: ElementTree.Element, stats
|
||||
if node.tag != "node":
|
||||
raise SkuEvidenceSanitizationError("原始节点树结构不匹配。")
|
||||
bounds = _parse_bounds(node.get("bounds"))
|
||||
_observe_bounds(stats, bounds)
|
||||
position = _vertical_position(bounds)
|
||||
if position == "private":
|
||||
# 私有带内的父节点不可以悄然包含下方子节点,否则会把仍需审计的下方内容一起丢失。
|
||||
if any(_vertical_position(_parse_bounds(descendant.get("bounds"))) != "private" for descendant in node.iter("node")):
|
||||
raise SkuEvidenceSanitizationError("原始节点树 bounds 结构不匹配。")
|
||||
for descendant in node.iter("node"):
|
||||
descendant_bounds = _parse_bounds(descendant.get("bounds"))
|
||||
_observe_bounds(stats, descendant_bounds)
|
||||
if _vertical_position(descendant_bounds) != "private":
|
||||
raise SkuEvidenceSanitizationError("原始节点树 bounds 结构不匹配。")
|
||||
stats.removed_nodes += sum(1 for _ in node.iter("node"))
|
||||
parent.remove(node)
|
||||
return
|
||||
@@ -294,11 +310,27 @@ def _parse_bounds(value: object) -> tuple[int, int, int, int]:
|
||||
if match is None:
|
||||
raise SkuEvidenceSanitizationError("原始节点树 bounds 缺失或无效。")
|
||||
left, top, right, bottom = (int(group) for group in match.groups())
|
||||
if not (0 <= left < right <= PRIVACY_MASK_CONFIG.width and 0 <= top < bottom <= PRIVACY_MASK_CONFIG.height):
|
||||
raise SkuEvidenceSanitizationError("原始节点树 bounds 超出脱敏配置。")
|
||||
if not (0 <= left < right and 0 <= top < bottom):
|
||||
raise SkuEvidenceSanitizationError("原始节点树 bounds 缺失或无效。")
|
||||
return left, top, right, bottom
|
||||
|
||||
|
||||
def _observe_bounds(stats: _CleanupStats, bounds: tuple[int, int, int, int]) -> None:
|
||||
_, _, right, bottom = bounds
|
||||
stats.max_right = max(stats.max_right, right)
|
||||
stats.max_bottom = max(stats.max_bottom, bottom)
|
||||
|
||||
|
||||
def _require_expected_xml_coordinate_space(stats: _CleanupStats) -> None:
|
||||
if (
|
||||
stats.max_right != PRIVACY_MASK_CONFIG.xml_width
|
||||
or stats.max_bottom != PRIVACY_MASK_CONFIG.xml_height
|
||||
):
|
||||
raise SkuEvidenceSanitizationError(
|
||||
f"原始节点树坐标范围不匹配(observed {stats.max_right}x{stats.max_bottom})。"
|
||||
)
|
||||
|
||||
|
||||
def _vertical_position(bounds: tuple[int, int, int, int]) -> str:
|
||||
_, top, _, bottom = bounds
|
||||
if bottom <= PRIVACY_MASK_CONFIG.privacy_top:
|
||||
@@ -364,15 +396,28 @@ def _derived_manifest(
|
||||
"schema_version": 1,
|
||||
"privacy_tier": "SANITIZED",
|
||||
"sanitizer_version": PRIVACY_MASK_CONFIG.version,
|
||||
"privacy_mask": {
|
||||
"width": PRIVACY_MASK_CONFIG.width,
|
||||
"height": PRIVACY_MASK_CONFIG.height,
|
||||
"rectangle": [0, 0, PRIVACY_MASK_CONFIG.width, PRIVACY_MASK_CONFIG.privacy_top],
|
||||
"screenshot_space": {
|
||||
"width": PRIVACY_MASK_CONFIG.screenshot_width,
|
||||
"height": PRIVACY_MASK_CONFIG.screenshot_height,
|
||||
"privacy_mask_rectangle": [
|
||||
0,
|
||||
0,
|
||||
PRIVACY_MASK_CONFIG.screenshot_width,
|
||||
PRIVACY_MASK_CONFIG.privacy_top,
|
||||
],
|
||||
},
|
||||
"xml_coordinate_space": {
|
||||
"width": PRIVACY_MASK_CONFIG.xml_width,
|
||||
"height": PRIVACY_MASK_CONFIG.xml_height,
|
||||
"privacy_mask_rectangle": [0, 0, PRIVACY_MASK_CONFIG.xml_width, PRIVACY_MASK_CONFIG.privacy_top],
|
||||
"observed_max": {"right": cleanup_stats.max_right, "bottom": cleanup_stats.max_bottom},
|
||||
},
|
||||
"privacy_cleanup": {
|
||||
"removed_nodes": cleanup_stats.removed_nodes,
|
||||
"cleared_crossing_nodes": cleanup_stats.cleared_crossing_nodes,
|
||||
"retained_below_nodes": cleanup_stats.retained_below_nodes,
|
||||
"max_right": cleanup_stats.max_right,
|
||||
"max_bottom": cleanup_stats.max_bottom,
|
||||
},
|
||||
"product": {"goods_id": link.goods_id},
|
||||
"human_declared_state": state,
|
||||
|
||||
@@ -19,8 +19,10 @@ sys.path.insert(0, str(CLIENT_ROOT / "src"))
|
||||
|
||||
from cmbuyer_client.device.sku_evidence_sanitizer import (
|
||||
EXPECTED_GOODS_ID,
|
||||
EXPECTED_HEIGHT,
|
||||
EXPECTED_WIDTH,
|
||||
EXPECTED_SCREENSHOT_HEIGHT,
|
||||
EXPECTED_SCREENSHOT_WIDTH,
|
||||
EXPECTED_XML_HEIGHT,
|
||||
EXPECTED_XML_WIDTH,
|
||||
HUMAN_DECLARED_STATES,
|
||||
SkuEvidenceSanitizationError,
|
||||
sanitize_sku_panel_evidence,
|
||||
@@ -59,14 +61,14 @@ def _write_raw(
|
||||
model: str = "PKG110",
|
||||
android_version: str = "16",
|
||||
pdd_version: str = "8.17.0",
|
||||
size: tuple[int, int] = (EXPECTED_WIDTH, EXPECTED_HEIGHT),
|
||||
size: tuple[int, int] = (EXPECTED_SCREENSHOT_WIDTH, EXPECTED_SCREENSHOT_HEIGHT),
|
||||
xml: str | None = None,
|
||||
) -> Path:
|
||||
raw = root / "raw"
|
||||
raw.mkdir(parents=True)
|
||||
screenshot = raw / "screenshot.png"
|
||||
image = Image.new("RGB", size, color=(0, 180, 0))
|
||||
if size == (EXPECTED_WIDTH, EXPECTED_HEIGHT):
|
||||
if size == (EXPECTED_SCREENSHOT_WIDTH, EXPECTED_SCREENSHOT_HEIGHT):
|
||||
for y in range(540):
|
||||
for x in range(8):
|
||||
image.putpixel((x, y), (255, 0, 0))
|
||||
@@ -115,11 +117,17 @@ class SkuEvidenceSanitizerTests(unittest.TestCase):
|
||||
self.assertNotIn(MASKED_PHONE, derived_xml)
|
||||
self.assertIn(f'"human_declared_state": "{state}"', manifest)
|
||||
self.assertIn('"privacy_tier": "SANITIZED"', manifest)
|
||||
self.assertIn('"sanitizer_version": "t103-privacy-v1"', manifest)
|
||||
self.assertIn('"rectangle": [', manifest)
|
||||
self.assertIn('"sanitizer_version": "t103-privacy-v2"', manifest)
|
||||
self.assertIn('"screenshot_space": {', manifest)
|
||||
self.assertIn('"xml_coordinate_space": {', manifest)
|
||||
self.assertIn('"height": 2376', manifest)
|
||||
self.assertIn('"height": 2400', manifest)
|
||||
self.assertIn('"privacy_mask_rectangle": [', manifest)
|
||||
self.assertIn('"removed_nodes": 1', manifest)
|
||||
self.assertIn('"cleared_crossing_nodes": 0', manifest)
|
||||
self.assertIn('"retained_below_nodes": 1', manifest)
|
||||
self.assertIn('"max_right": 1080', manifest)
|
||||
self.assertIn('"max_bottom": 2400', manifest)
|
||||
self.assertNotIn("canonical_url", manifest)
|
||||
self.assertNotIn(TEST_SERIAL, manifest)
|
||||
self.assertNotIn("serial", manifest)
|
||||
@@ -149,7 +157,13 @@ class SkuEvidenceSanitizerTests(unittest.TestCase):
|
||||
manifest = json.loads(result.manifest_path.read_text(encoding="utf-8"))
|
||||
self.assertEqual(
|
||||
manifest["privacy_cleanup"],
|
||||
{"removed_nodes": 1, "cleared_crossing_nodes": 1, "retained_below_nodes": 1},
|
||||
{
|
||||
"removed_nodes": 1,
|
||||
"cleared_crossing_nodes": 1,
|
||||
"retained_below_nodes": 1,
|
||||
"max_right": 1080,
|
||||
"max_bottom": 2400,
|
||||
},
|
||||
)
|
||||
|
||||
def test_same_raw_and_config_produce_identical_derived_files(self) -> None:
|
||||
@@ -167,6 +181,26 @@ class SkuEvidenceSanitizerTests(unittest.TestCase):
|
||||
):
|
||||
self.assertEqual(left_path.read_bytes(), right_path.read_bytes())
|
||||
|
||||
def test_manifest_records_distinct_screenshot_and_xml_coordinate_spaces(self) -> None:
|
||||
with TemporaryDirectory() as temporary:
|
||||
raw = _write_raw(Path(temporary))
|
||||
result = sanitize_sku_panel_evidence(raw, raw.parent / "derived")
|
||||
manifest = json.loads(result.manifest_path.read_text(encoding="utf-8"))
|
||||
|
||||
self.assertEqual(
|
||||
manifest["screenshot_space"],
|
||||
{"width": 1080, "height": 2376, "privacy_mask_rectangle": [0, 0, 1080, 540]},
|
||||
)
|
||||
self.assertEqual(
|
||||
manifest["xml_coordinate_space"],
|
||||
{
|
||||
"width": 1080,
|
||||
"height": 2400,
|
||||
"privacy_mask_rectangle": [0, 0, 1080, 540],
|
||||
"observed_max": {"right": 1080, "bottom": 2400},
|
||||
},
|
||||
)
|
||||
|
||||
def test_hash_and_metadata_mismatch_fail_closed_without_leak(self) -> None:
|
||||
scenarios = (
|
||||
("hash", {}, "screenshot"),
|
||||
@@ -175,7 +209,8 @@ class SkuEvidenceSanitizerTests(unittest.TestCase):
|
||||
("version", {"pdd_version": "8.17.1"}, None),
|
||||
("goods", {"goods_id": "123"}, None),
|
||||
("state", {"state": "guessed"}, None),
|
||||
("resolution", {"size": (100, 100)}, None),
|
||||
("old-screenshot-space", {"size": (1080, 2400)}, None),
|
||||
("other-screenshot-space", {"size": (100, 100)}, None),
|
||||
)
|
||||
for name, kwargs, corrupt_file in scenarios:
|
||||
with self.subTest(name=name), TemporaryDirectory() as temporary:
|
||||
@@ -281,6 +316,44 @@ class SkuEvidenceSanitizerTests(unittest.TestCase):
|
||||
self.assertFalse((raw.parent / "derived").exists())
|
||||
self.assertEqual(list(raw.parent.glob(".derived.staging-*")), [])
|
||||
|
||||
def test_xml_coordinate_space_must_have_exact_configured_maximums(self) -> None:
|
||||
scenarios = (
|
||||
(
|
||||
"short-width",
|
||||
"<hierarchy><node bounds='[0,0][1079,540]' text='private' />"
|
||||
"<node bounds='[0,540][1079,2400]' text='safe' /></hierarchy>",
|
||||
"1079x2400",
|
||||
),
|
||||
(
|
||||
"short-height",
|
||||
"<hierarchy><node bounds='[0,0][1080,540]' text='private' />"
|
||||
"<node bounds='[0,540][1080,2376]' text='safe' /></hierarchy>",
|
||||
"1080x2376",
|
||||
),
|
||||
(
|
||||
"wide-width",
|
||||
"<hierarchy><node bounds='[0,0][1081,540]' text='private' />"
|
||||
"<node bounds='[0,540][1081,2400]' text='safe' /></hierarchy>",
|
||||
"1081x2400",
|
||||
),
|
||||
(
|
||||
"tall-height",
|
||||
"<hierarchy><node bounds='[0,0][1080,540]' text='private' />"
|
||||
"<node bounds='[0,540][1080,2401]' text='safe' /></hierarchy>",
|
||||
"1080x2401",
|
||||
),
|
||||
)
|
||||
for name, xml, observed in scenarios:
|
||||
with self.subTest(name=name), TemporaryDirectory() as temporary:
|
||||
raw = _write_raw(Path(temporary), xml=xml)
|
||||
with self.assertRaises(SkuEvidenceSanitizationError) as raised:
|
||||
sanitize_sku_panel_evidence(raw, raw.parent / "derived")
|
||||
|
||||
self.assertIn(observed, str(raised.exception))
|
||||
self.assertNotIn(TEST_SERIAL, str(raised.exception))
|
||||
self.assertFalse((raw.parent / "derived").exists())
|
||||
self.assertEqual(list(raw.parent.glob(".derived.staging-*")), [])
|
||||
|
||||
def test_manifest_schema_package_and_artifact_structure_are_required(self) -> None:
|
||||
def mutate(manifest: dict[str, object], kind: str) -> None:
|
||||
if kind == "schema":
|
||||
|
||||
+12
-8
@@ -152,20 +152,24 @@ T-103 已证明当前衣服商品没有独立「规格/已选」入口。T-110
|
||||
PDD 规格面板不可避免显示收货区域和掩码手机号。原始截图/XML 只能留在
|
||||
`%LOCALAPPDATA%\cmbuyer\artifacts\...\raw` 隔离目录,不供 agent、fixture、业务或 HTTP 上传读取;
|
||||
T-103 已在 `client/src/cmbuyer_client/device/sku_evidence_sanitizer.py` 实现本机确定性脱敏器,并由
|
||||
`client/scripts/sanitize_sku_panel_evidence.py` 提供离线 CLI。当前 `t103-privacy-v1` 配置精确绑定
|
||||
PKG110 / Android 16 / 拼多多 8.17.0 / goods_id `937122477375` / 1080×2400,把截图的
|
||||
`[0,0,1080,540)` 整宽隐私带遮罩,并按相同几何边界递归移除 XML 节点;跨界容器只清空自身敏感属性,
|
||||
保留下方子节点。地址依靠已确认的整块几何隔离而不是易漏的关键词表;完整、掩码、带分隔符或跨节点
|
||||
手机号残留会被自动复检拒绝。
|
||||
`client/scripts/sanitize_sku_panel_evidence.py` 提供离线 CLI。第一组新 raw 的 PNG 头部由人确认实际为
|
||||
1080×2376,推翻了 v1 将截图和 XML 坐标空间都写成 1080×2400 的假设;v1 正确拒绝且原始证据未重采。
|
||||
当前 `t103-privacy-v2` 配置精确绑定 PKG110 / Android 16 / 拼多多 8.17.0 / goods_id
|
||||
`937122477375`,分别约束 screenshot space=1080×2376、XML coordinate space=1080×2400,二者都把
|
||||
`[0,0,1080,540)` 作为整宽隐私带。截图覆盖该区域;XML 递归移除区域内节点,跨界容器只清空自身
|
||||
敏感属性并保留下方子节点。地址依靠已确认的整块几何隔离而不是易漏的关键词表;完整、掩码、带分隔符
|
||||
或跨节点手机号残留会被自动复检拒绝。
|
||||
|
||||
```powershell
|
||||
.\client\.venv\Scripts\python.exe client\scripts\sanitize_sku_panel_evidence.py --raw-dir "<证据目录>\raw" --output-dir "<证据目录>\derived"
|
||||
```
|
||||
|
||||
脱敏器校验源 manifest 与文件哈希、设备/App/商品/人工状态、分辨率和 XML 隐私结构,只允许发布到
|
||||
脱敏器校验源 manifest 与文件哈希、设备/App/商品/人工状态、截图分辨率和 XML 隐私结构;XML 所有
|
||||
节点观察到的最大 right/bottom 必须精确为 1080×2400,否则只报告非敏感 observed 尺寸并拒绝。它只允许发布到
|
||||
同级且尚不存在的 `derived`;失败或发布竞态不覆盖已有目录、不留下 staging。派生 manifest 记录
|
||||
`privacy_tier=SANITIZED`、sanitizer 版本、清理计数及本机 source/derived 哈希,不记录原始路径、serial
|
||||
或页面正文。只有自动复检通过并经人确认状态对应性的派生物,agent 才能读取并提取最小 fixture、编写判据。
|
||||
`privacy_tier=SANITIZED`、sanitizer 版本、两个坐标空间、清理计数及本机 source/derived 哈希,不记录
|
||||
原始路径、serial 或页面正文。只有自动复检通过并经人确认状态对应性的派生物,agent 才能读取并提取
|
||||
最小 fixture、编写判据。
|
||||
|
||||
2026-08-04 的首轮旧证据只用于确认上述入口事实;其中旧 `initial` 不是规格面板,另两张原始截图含隐私
|
||||
区域,因此 XML 未读取、fixture 与选择器未生成。T-110 完成受控入口与脱敏契约后,T-103 重新取证;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
`client/` 已有 Python 包、PySide6 最小入口、运行目录与日志脱敏策略,以及显式 serial 的 ADB
|
||||
连接边界、本地基线取证 CLI、受限商品链接打开取证 CLI、人工声明规格面板状态的只读取证 CLI,
|
||||
以及绑定 PKG110 / Android 16 / 拼多多 8.17.0 的规格证据确定性脱敏 CLI;尚无规格选择、价格读取或下单流程
|
||||
- 测试:采购服务已覆盖健康检查、核心模型、迁移与状态机等离线包级测试;采购工具 67 项离线单元测试
|
||||
- 测试:采购服务已覆盖健康检查、核心模型、迁移与状态机等离线包级测试;采购工具 69 项离线单元测试
|
||||
(全部 mock,不连接真机)
|
||||
- 数据:SQLite 核心表与迁移已落成;无业务实例数据
|
||||
- 标准启动路径:Windows PowerShell 运行 `./init.ps1`,Unix shell 运行 `./init.sh`。Windows 入口
|
||||
@@ -33,8 +33,8 @@
|
||||
打开规格面板;T-110 已获项目所有者批准,只把该证据/版本绑定的精确唯一入口作为第一趟可逆导航,
|
||||
数量、确认页、提交订单、付款与通用点击能力仍不可达。T-103 的原始证据本机隔离与确定性脱敏器
|
||||
已完成离线实现;人工确认面板刚打开时目标颜色和尺码已经自动选中,取证三态已据此修正。下一步
|
||||
采集“刚打开目标预选 / 两维度改为非目标 / 两维度恢复目标”的 raw 并由人只核对 derived。在派生
|
||||
证据验收前不写页面判据,Phase 2 仍不能抢跑。
|
||||
用 v2 对已采第一态 raw 重新脱敏并验证 XML 坐标空间,再采集“两个维度改为非目标 / 两个维度恢复
|
||||
目标”的 raw,由人只核对 derived。在派生证据验收前不写页面判据,Phase 2 仍不能抢跑。
|
||||
|
||||
## 当前目录要点
|
||||
|
||||
@@ -161,7 +161,8 @@ T-103 已确认当前衣服商品只能从精确文案“快要抢光”进入
|
||||
```
|
||||
|
||||
原始 screenshot/XML 可能包含 PDD 固定展示的地址和掩码手机号,只能留在本机 `raw` 目录,不能由
|
||||
agent、fixture、业务或上传端消费。脱敏器会校验源哈希、设备/App/商品/分辨率与隐私结构,在 sibling
|
||||
agent、fixture、业务或上传端消费。`t103-privacy-v2` 分别固定截图 1080×2376 与 XML 坐标
|
||||
1080×2400,并校验源哈希、设备/App/商品/人工状态与隐私结构,在 sibling
|
||||
`derived` 目录原子发布 screenshot/XML 与 manifest;任何不匹配、手机号残留或已有目标均拒绝发布。
|
||||
自动复检通过且人确认三态对应性后,agent 才能读取派生物并提取最小 fixture、编写判据。
|
||||
人工确认中还必须记录中间态实际选择的非目标颜色和尺码,不能只写“已切换”。
|
||||
|
||||
+9
-1
@@ -25,7 +25,7 @@ write_paths:
|
||||
- docs/current-state.md
|
||||
---
|
||||
|
||||
<!-- BEGIN VIKUNJA EXPORT id=23 synced=2026-08-04T03:40:57Z sha256=a99035ed330955130744a76652ba0ffc278aeda1c1b73f4cf8060185a81800ee -->
|
||||
<!-- BEGIN VIKUNJA EXPORT id=23 synced=2026-08-04T03:59:02Z sha256=07472f92450a4ec3e062f779461c29a40f0078b17a6d58745e529f0c855a64cf -->
|
||||
## 问题 / 背景
|
||||
|
||||
T-102 已证明 canonical 链接可进入目标商品。T-103 随后在 PKG110 / Android 16 / 拼多多 8.17.0、衣服商品 goods_id `937122477375` 上确认:规格面板只能从详情页右下角精确文案“快要抢光”进入,面板固定显示收货区域和掩码手机号。T-110 经项目所有者批准,将该已取证点击定义为可逆、能力受限的规格面板导航;这不是通用购买入口豁免,不授权其他文案、数量、确认页、提交订单或支付。 项目所有者随后确认,面板刚打开时已自动选中目标颜色“黑色CHA(纯棉)”和尺码“M(建议100-115)”;因此本任务不再假设存在“未选择”或“只选择一个维度”的初始状态。
|
||||
@@ -115,6 +115,14 @@ T-102 已证明 canonical 链接可进入目标商品。T-103 随后在 PKG110 /
|
||||
### 2026-08-04T03:40:39Z · ila
|
||||
|
||||
T-103 新三态离线实现与主审:提交 dbb69a7 将人工声明状态集中到 sku_panel_state.py,采集器和脱敏器仅接受 panel-opened-target-preselected、alternate-all-dimensions-selected、target-selection-restored;旧 initial / one-dimension-selected / all-dimensions-selected 与未知值均 fail closed。主 agent 独立复跑 67 项 client 单测、compileall、两个 CLI help、静态禁用能力检索与完整 init.ps1,全部通过。过程未连接设备、未读取真实 raw、未新增页面判据或点击/数量/确认/提交/付款能力。T-103 保持 DOING,等待新三态 derived 人工验收。
|
||||
|
||||
### 2026-08-04T03:54:21Z · ila
|
||||
|
||||
第一组新三态 raw 已由人成功采集;脱敏器 v1 按设计拒绝。人只读取 PNG 头部元数据并确认实际 format=PNG、size=1080x2376,证明 v1 将截图尺寸与 Android XML 坐标空间统一设为 1080x2400 的假设错误。原始证据不重采、不删除、不读取正文;下一步把 screenshot 与 XML coordinate space 分离配置,升级 sanitizer 版本并继续 fail closed。
|
||||
|
||||
### 2026-08-04T03:58:54Z · ila
|
||||
|
||||
T-103 sanitizer v2 坐标修正与主审:提交 44c027a 将 screenshot space 固定为人确认的 1080x2376,并把 XML coordinate space 独立严格配置为待验证的 1080x2400;sanitizer_version 升为 t103-privacy-v2。XML 会统计所有 bounds 的 max right/bottom,任何小于或大于配置的偏差均 fail closed,错误只含非敏感 observed 尺寸。派生 manifest 记录两个坐标空间和各自遮罩。主 agent 独立复跑 69 项 client 单测、compileall、CLI help 与 diff-check,全部通过;未读取真实 raw 正文、未连接设备。等待人用同一第一态 raw 重跑 v2 脱敏。
|
||||
<!-- END VIKUNJA EXPORT -->
|
||||
|
||||
## 边界
|
||||
|
||||
Reference in New Issue
Block a user