跳转至

验证

已验证提交:write-plan 分支上的 235b22f2,位于 7056f5ff。在 macOS 上使用 Node v22.23.1 执行(仓库在 .nvmrc 中固定为 24.18.0,但未安装);在线应用运行在六台一次性 Linux 虚拟机上。

门禁

  • npm run build:cached、npm run check:fast(类型检查和 lint)、npm run format:check、npx knip、 npm run scan:todos、npm run docs:check、npm run verify:test-discovery 和 npm run mcp-catalog:check:通过。
  • npm run jscpd:3609 个文件中 3.46% 的重复行,低于 3.5% 的门槛。
  • npx vitest run --project domain --project services:7487 通过,5 个既有跳过。
  • npm run test:prepare,然后 npm run test:focused -- 涵盖 environment-compare、export 和 serve 的 CLI 边界、 serve 的 MCP 边界以及工具 schema 有效性边界:5 个文件,30 通过。

新增测试:匹配、ID 映射与转换、配对守卫、规范化,以及每个类别的单元测试 (environmentCompareIds、environmentCompareDiff、environmentCompare,19 个测试),以及通过构建后的 CLI 执行该命令(environmentCompareCli,2 个测试):一个对书籍应用在其他 ID 下重新播种后的探测,在映射了 2 个 ID 的情况下每一步都一致;一个不是捕获 Evidence 的探测会连同其索引被拒绝。

六种环境与其在线应用

六个已发布环境与各自捕获自的应用进行了比较:NetBox 4.7.3、Komga 1.29.1、 Mealie 3.28.0、n8n 1.123.85、Shlink 5.1.7 和自托管 Sentry 26.9.0,使用与捕获时相同的镜像,由相同的脚本拉起并 播种数据,均通过 loopback 进行。每个环境在播种后创建快照,并在每个序列(7 到 80 秒)前从该快照恢复,因此每个序列 面对的都是同一个应用。

REA 之外的一个驱动对每个应用运行三类序列,每类都以捕获的只读前缀开始: 按顺序重放捕获;对每种所提供的写入类型,依次执行 list、create、read、update、read、delete、read 和 list,然后执行 一次更改了自由文本的第二个 create;以及通过其 item 工具读取每个已播种实体。它在学习到环境 ID 的同时将其转换为应用 的 ID,使用应用自身的凭据登录,并将每个序列写成不带 header 或 cookie 的 HAR,再由 inspect-web-network-capture 转换为探测 Evidence。各环境的 manifest 使用 prefix-variance,因此比较是在 该分支的本地构建上运行,并合并了该规则所在的分支。

Environment 步骤 一致率 忽略顺序后 与自身捕获对比
NetBox 370 99.2% 99.2% 100%
Komga 356 93.8% 93.8% 93.1%
Mealie 301 96.0% 97.0% 97.1%
Shlink 133 89.5% 92.5% 92.4%
n8n 202 74.8% 87.6% 87.5%
Sentry 230 23.9% 46.5% 84.7%

最后一列将每个环境与其自身的捕获作为探测进行对比:即使是对其构建自的会话,它也无法复现的内容。不一致分为四类。

  • 环境缺口(需要修复的部分):
  • 环境不提供的写入工具,以及随之而来的读取:阅读列表和收藏(Komga)、食谱(Mealie)、短 URL (Shlink)、工作流和标签写入(n8n);
  • 前置条件:n8n 拒绝删除未归档的工作流,而环境却将其删除;
  • 列表:新建项没有出现在保持记录成员不变的列表中(Komga);过滤列表在应答时未应用过滤条件(Shlink);重放的搜索忽略了请求体(Komga);
  • 一个带 domain 作用域的短 URL 在应答时缺少其 domain 参数,而 Shlink 返回 404;
  • 状态渲染:两个已记录视图不一致的项目字段被渲染为单一值;形状取决于查询参数的组织读取,无论参数如何都返回相同结果(Sentry);
  • 标签读取因缺少其 item 视图始终携带的字段而被拒绝(Mealie);
  • 时间:按小时统计的事件数随当前时间变化,而环境的时钟冻结在捕获时刻(Sentry);
  • 捕获时被脱敏替换的字段,其应答为 "<scrubbed>"(Sentry)。
  • 应用在不同播种之间与自身不一致:Sentry 返回的权限列表顺序不固定,重新播种改变了 release 的首次事件时间、功能列表和 profile 标志;n8n 的执行记录和 NetBox 的运行中 worker 数量与捕获时的实例不同。
  • 探测观察到自身:Komga 和 Sentry 报告的是驱动自身的登录 user agent。
  • 驱动缺口:驱动从未映射的 ID(Mealie 单位、Sentry 按 hash 的事件)被当作环境的 ID 发送。

运行对比较的修正

每次在线运行都发现了比较本身出错的地方,并在下一次运行之前用测试修复:请求按环境的术语匹配到工具,因此路径中包含已播种 ID 的工具会字面匹配;ID 仅在标识符字段之外从字符串中学习,此前 NetBox 的 worker 数量 1 将在线环境的 0 映射到了实体 1;仅按位置配对的条目只有在不共享键且长度一致时才学习 ID,此前一个过滤后的 Shlink 列表将一个短 URL 映射到了另一个;以 ID 为键的对象会配对它们的条目(n8n 数据表);重排的标量集合只在顺序上不同,数组中的 epoch 数字是时钟(Sentry);超过重放限制的请求体按状态进行比较。

评审轮之后

该分支在 b6c3b168 处合并了 write-plan 分支(其评审发现了四个与 serving 和门禁相关的修复),随后在九个地方修改了比较逻辑:ID 仅从调用自身的应答中学习,并且也按集合分别学习;分页值永不被转换;调用经过所提供工具的输入 schema;浏览器探测保留其请求体;空请求体和大型二进制请求体按服务器应答的方式比较;标量集合在映射后比较;以 ID 为键的对象在部分键重叠时进行配对。每个新测试在之前的 HEAD 上都会失败。上述门禁再次通过(domain 和 services 7504 通过,5 个既有跳过;聚焦边界 80;一致性测试;jscpd 3.46%)。在相同探测上重新运行,并合并了该分支与模板规则所在分支后,六种环境的比较结果与上表完全一致,逐步逐项相同,映射的 ID 相同,规范化的值也相同。

第二轮,根据 Greptile 对这些更改的审查,仅当整个记录行和请求体与调用匹配时,才从回放中学习 id;在调用的集合内比较实体自身的身份,并使用探针的映射比较其他 id;仅当重叠的键明显是 id 时才将其读作 id;并将 JSON 空字符串与空文本区分开。其四项测试在先前 head 上失败;六个环境再次与表中的结果完全一致。

第三轮在服务端环境所定位的位置找到答案自身的实体——位于读取视图的实体或列表指针处,或写入的响应指针处——这样信封内的实体保留其集合的映射。其测试在先前 head 上失败;门禁再次通过(领域与服务 7509,边界 80,一致性,jscpd 3.46%),六个环境再次与表中的结果完全一致。

第四轮,根据 Greptile 的下一轮审查,将探针未观察到的内容排除在一致性数字之外。没有可用请求行的记录,以及没有记录响应的请求,都会按探针列出原因并保持不比较。探针未记录的实时主体仅按状态比较并计入。未捕获响应体的浏览器探针被拒绝。格式规则路径参数按其解码值匹配,正如重放读取它时那样。该分支还合并了 1d1c4787 处的 write-plan 分支,其门禁不再通过读取后来写入所替换的状态来确认一次写入。每个新测试都在先前 head 上失败。门禁再次通过(领域与服务 7647,5 个预先存在的跳过;比较、导出、服务和分析视图边界 69;一致性 279;jscpd 3.44%)。

在相同的探针上,六个环境没有未观察到的记录或主体,其中五个与之前完全一致。Komga 现在不再提供其集合创建和补丁。门禁唯一能检查它们的方式是删除集合后的 404,因为补丁后的读取是补丁自身的观察结果。其一致性从 96.9% 下降到 93.8%:探针进行的三个创建和两个补丁未被服务,随后对这些集合的六个读取和删除失败。相对于其自身捕获,一致性从 96.6% 下降到 93.1%。表中显示了新的数值。

第五轮合并了 dc5e2aad 处的 write-plan 分支,该分支在后续写入替换更新时衡量其派生更改,并在重置或加载时重置集合拼写。它还让探针丢失了响应的请求保持未比较,以便后续读取看到其效果:丢失的删除后跟 404 现在一致。其测试在先前 head 上失败。六个环境与表中的结果一致。

第六轮合并了 fe8ab584 处的 write-plan 分支,其门禁会从较早的写入效果中擦除被后续写入和已记录结果覆盖的字段。六个环境再次与表中的结果完全一致。

第七轮合并了 3580d85c 处的 write-plan 分支和 d8a9673f 处的主分支。这带来了门禁针对被覆盖的文档写入的修复,这也是 Greptile 在此处针对 write-plan 代码提出的问题之一。它还修复了自身的两个发现:

  • 带有任何未注明日期的记录的探针会保持其记录顺序,正如状态提取所做的那样;
  • 没有记录请求体的浏览器探针如果发起 POST、PUT 或 PATCH 调用,则被拒绝。

两个测试都在先前 head 上失败。在探针及其自身捕获方面,六个环境与表中的结果完全一致。

第八轮合并了 43d98ea9 和 264ce410 处的 write-plan 分支。它们带来了门禁对 Greptile 在此处针对 write-plan 代码提出的三个发现的修复:一次通过但未显示写入任何内容的读取会使其保持待处理状态;更新或删除像条目读取那样查找其实体;构建的文档计划在其自身对象上写入后续调用。它还修复了自身的一个发现:对探针应用程序之外的其他来源的请求既不会发出也不会比较,因此另一台服务器的 DELETE /api/books/2 不再删除环境中的书。其测试在先前 head 上失败。在探针及其自身捕获方面,六个环境与表中的结果完全一致,且没有未观察到的记录。

随后它合并了 0ae64400 和 6257de93 处的 write-plan 分支:更新的身份成员绝不采用规则,零页大小不限制任何内容,并且分页规则随该分支的 isPagingParameter 一起移至 environmentStatePaging.ts。六个环境再次与表中的结果完全一致。

第九轮合并了 466ed8b4 处的 write-plan 分支,它修复了 Greptile 在此处针对其代码提出的两个发现(看起来像指针的实体字段按名称匹配;只有当记录在其下列出项目时,零页大小才不限制任何内容)以及其他问题。在探针及其自身捕获方面,六个环境与表中的结果完全一致。随后它合并了 7cef2fb2,其修复解决了 Greptile 在此处针对该分支代码提出的另外三个发现:零页大小擦除了另一个边界,部分写入响应被当作删除,以及由工具在其他地方的调用所计时的干扰。六个环境再次与表中的结果完全一致。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 rea2/rea