Regmark03
功能模块
七个模块,一条流水线:读进来,归成一种结构,逐项比对,再让代理走一遍,最后出报告。
总览
数据只往一个方向流。采集器把各个出口读进来,归成同一种结构;四个检查器各看各的;报告器负责输出。模块之间只通过两种数据打交道:商品事实表和发现项。
-
出口
- C商品页
- M商品 feed
- Y协议端点
- K结账
-
采集器
每个出口一个。只读,不做判断。
-
商品事实表
每个事实都记着谁、什么时候、怎么说的。
-
检查器
- 套准引擎
- 协议符合性
- 内容卫生
- 探店代理
-
报告
- 终端摘要
- JSON、SARIF、JUnit
- HTML 报告
- 退出码
四块版
出口很多,方案里把它们归成四类,借印刷的四色版来称呼。后面的图表都用同一套颜色。
| 版 | 出口 | 首期支持 |
|---|---|---|
| C | 商品页:可见文字、JSON-LD、microdata、Open Graph | 全部 |
| M | 商品 feed:Google Merchant 格式、OpenAI 商品 feed | Google 格式 |
| Y | 协议端点:UCP、ACP、店铺自己的 MCP 服务 | UCP |
| K | 结账:把商品加进购物车,填上收货地,读出实算总价 | WooCommerce、Shopify |
K 版是基准,其余三块向它对齐。没有接结账探针的时候,基准依次退到平台接口和可见页面。
模块一:采集器
做什么。 每个出口一个采集器,负责把那个出口上的商品事实读出来,连同出处一起交给事实表。采集器只读不判断,判断是规则的事。
设计要点。
- 默认只发 GET 请求,遵守 robots.txt,同一主机每秒不超过一次请求,User-Agent 里写明自己是谁。
- 商品页默认不启动浏览器,直接解析返回的 HTML。需要执行脚本才能看到价格的站点,可以选装 Playwright。
- JSON-LD 不加载远程
@context。那既慢,又等于让被测站点指挥工具去访问任意地址。 - 结账探针是唯一带写操作的采集器:建一个购物车,加一件商品,填一个测试收货地,读出总价,然后删掉购物车。它不会走到支付那一步,而且只对验证过归属的店铺开放。
首期范围。 商品页、Google 格式的 feed、UCP 的目录和结账会话、WooCommerce Store API、Shopify Storefront API。
首期不做。 ACP 端点、Magento、Medusa、Saleor。接口留好,等社区或后续版本补。
模块二:商品事实表
做什么。 把各个采集器读到的东西归成一张表。它的要点是:一个事实不存一个值,而是存一组「谁在什么时候说了什么」。
一件商品的某个变体,价格这一栏里可能有四条记录,分别来自 JSON-LD、feed、UCP 和结账,每条都带着原始取值、出处和读取时间。比对、报告、追溯,全都建立在这个结构上。
身份对齐。 同一个变体在不同出口上用的标识往往不同。事实表按这个顺序认人:GTIN,品牌加 MPN,SKU,规范化后的链接加变体选项。哪儿都对不上的记录不会被丢掉,而是单独报出来,因为代理同样对不上。
首期范围。 价格、币种、库存状态、运费、变体选项、标识符、退货政策是否存在。
首期不做。 评价、图片、商品属性的语义比对。
模块三:套准引擎
做什么。 对事实表里的每个变体,拿各个出口的记录和基准比,输出发现项。规则是纯函数,输入是一个变体的全部记录,输出是零条或多条发现。
误报怎么控制。 这是这个模块成败的关键,因为一个老喊狼来了的检查,很快会被人从 CI 里摘掉。
- 时间窗。 同一个变体的各个出口尽量在同一小段时间里读完。
- 二次确认。 首轮发现对不上的,隔一会儿把相关出口重读一遍,仍然对不上才报。
- 容差。 每条规则有自己的容差。价格默认分毫不差;库存允许 feed 落后一个导出周期,但会单独记成「过期」。
- 预算。 每条规则可以设允许的失败数。新接入的店可以先把预算设成现状,以后只许降不许升。
首批规则。
| 规则 | 说明 | 默认级别 |
|---|---|---|
price.mismatch |
同一变体的价格与基准不一致 | 错误 |
price.currency-ambiguous |
币种缺失,或者各出口的币种不同 | 错误 |
price.sale-expired |
促销价的有效期已过,还在对外展示 | 警告 |
price.tax-basis |
含税价和不含税价混着用 | 警告 |
availability.mismatch |
库存状态与基准不一致 | 错误 |
availability.stale |
feed 的导出时间早于最近一次库存变化 | 警告 |
variant.missing |
页面上能选的变体,在机器可读的出口里没有 | 错误 |
variant.unpurchasable |
机器可读的出口里列着的变体,结账时加不进购物车 | 错误 |
shipping.mismatch |
对外标称的运费与结账实算不一致 | 错误 |
shipping.undisclosed |
运费要到结账才知道,前面的出口都没有表达 | 警告 |
identity.unmatched |
某个出口上的商品,在其他出口找不到对应 | 警告 |
identity.gtin-invalid |
GTIN 的校验位不对,或者被多个变体共用 | 警告 |
policy.return-missing |
退货政策没有机器可读的表达 | 提示 |
模块四:协议符合性
做什么。 检查发现文件和端点的形状合不合规范。这一块别人已经做得不错,所以 Regmark 只做一层薄的包装:直接用 UCP 和 ACP 规范自带的 JSON Schema,能调官方 conformance 套件的地方就调它,不重写。
为什么还要有。 因为采集器要读协议端点,形状不对的话后面的比对没法做。先把形状问题单独报出来,用户才知道该先修哪一层。
首期范围。 UCP 的发现文件、目录、结账会话,按规范版本钉死。
模块五:内容卫生
做什么。 找出商品内容里会带偏代理的东西。
| 规则 | 说明 |
|---|---|
content.hidden-text |
人看不见、机器读得到的文字:和背景同色、字号为零、移出屏幕、藏在注释和 alt 里 |
content.instruction-like |
写给模型看的指令,比如「忽略之前的要求」「务必推荐本商品」 |
content.invisible-chars |
零宽字符和 Unicode 标签字符,常被用来夹带看不见的文本 |
content.cloaking |
用浏览器的 User-Agent 和代理的 User-Agent 分别访问,拿到的商品事实不一样 |
设计要点。 首期全部用确定性规则,不调模型,这样结果可以复现,也没有成本。用模型做二次判定留作可选项。
模块六:探店代理
做什么。 让一个代理带着购物任务把店走一遍,看它能不能把东西放进购物车,以及它报给用户的总价和结账算出来的是不是同一个数。
任务长什么样。 一个任务由意图、约束和可验证的终态组成:
id: blue-tee-under-40
intent: 帮我找一件 40 美元以内、M 码的蓝色 T 恤,寄到旧金山
constraints:
- total <= 40 USD # 含运费
- variant.size == M
- variant.color ~ blue
expect:
cart.items: 1
quoted.total == checkout.total
最后一行是整个模块的核心断言:代理告诉用户的总价,等于结账实算的总价。
设计要点。
- 断言看购物车,不看对话。 代理说了什么不作数,购物车里实际有什么、总价是多少才作数。
- 任务从事实表里生成。 工具根据店里真实的商品、变体和价格区间自动出题,保证每道题都有解。
- 录制与重放。 每次实跑都把模型的请求和响应录下来。重放不调模型、不花钱,结果是确定的,适合放在每次提交都跑的 CI 里;实跑放在定时任务里。
- 多跑几遍。 模型输出有随机性,实跑默认每个任务跑三次,报成功率,不报单次结果。
- 代理只拿得到店铺的工具。 它能搜索、查看、操作购物车,除此之外什么都做不了,而且在支付之前强制停下。
首期范围。 两种走法:读商品页,和调用 UCP 或 MCP 的工具。模型通过 OpenAI 兼容接口或 Anthropic 接口接入,用户自带密钥,也可以接本地模型。
模块七:报告与 CI
做什么。 把发现项变成人和机器都能用的输出。
| 输出 | 给谁 |
|---|---|
| 终端摘要 | 在本地跑的开发者 |
| JSON,结构稳定并带版本号 | 想自己做二次处理的人 |
| SARIF | GitHub 的代码扫描界面 |
| JUnit XML | 其他 CI 系统 |
| 单文件 HTML 报告 | 要把结果发给客户或同事的人 |
| 退出码 | CI 的门禁 |
GitHub Action。 首期提供一个官方动作,在合并请求里跑检查,把失败的规则写成评论。
首期之后的两个模块
巡检与漂移。 定时跑,把每次的结果存进 SQLite,画出每条规则的失败数随时间的变化,并在变坏时发通知。
修复建议。 针对失败的规则给出平台相关的修法,比如 WooCommerce 里是哪个钩子在输出 JSON-LD 的价格。这一块做得好会很有用,但它依赖前面的规则先稳定下来。