From c55768233cc481d44f0023f27df4e2b5577b9e80 Mon Sep 17 00:00:00 2001 From: czcdr Date: Mon, 26 Oct 2026 21:16:33 +0800 Subject: [PATCH] fix: the setup card must say what setup resets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restarting dsh with the 2.0.12 copy still in the profile re-provisioned the bundle to 2.0.12's pin: REA went back to 6.7.0, the Ghidra gate override went with it, and the MCP row failed with "REA could not start". That is the card doing its job on a machine whose plugin copy was stale — but the card never said the aligned run also wipes the override. - client.js: third note in both languages. It names the override case and the command that restores it, so the card is not advice that silently undoes a deliberate choice. - README (both): the "you must run setup" block says the same thing. - ghidra-gate-override.mjs: judge a recorded override by the files on disk. Re-provisioning replaces rea-agents while leaving the record behind, so --enable now replaces a stale record instead of refusing, and --disable counts already-lost files instead of throwing. - CHANGELOG: 2.0.13 records both, plus the failure that prompted them. Verified after re-aligning this machine: setup --reuse reached "setup complete" with its Ghidra smoke importing java.exe in 28 s on 12.2, the shim resolves REA 6.8.0, and the MCP path through the shim returns the same segments / procedure inventory / entry pseudocode as the 12.1.4 baseline with provider.version = 12.2. Gates: audit 24/24 hash-pinned clean, 88 tests (85 pass / 3 skipped), verify-branch ALL CHECKS PASSED, host-compat 10 covered / 5 outside. --- CHANGELOG.md | 9 +++++++ README.md | 2 ++ README.zh-CN.md | 3 ++- client.js | 3 +++ scripts/ghidra-gate-override.mjs | 40 +++++++++++++++++++++++++++++--- 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a579298..b2c038d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,15 @@ - **`check-updates.ps1` 认得这份记录**:盘上跑 12.2 时打印「本机覆盖 Ghidra 闸门」,而不是劝用户「重跑 setup 对齐」 —— 后者正好会把覆盖悄悄抹掉。 +### 修复 + +- **「还要跑一次 setup」卡片的说明补上覆盖这一条**:这张卡片出现在每次升级之后,而它就是叫你去跑 setup 的。 + setup 会重新供给内置 REA,**Ghidra 闸门覆盖随之消失** —— 卡片现在明写这一点并给出恢复命令。踩到的实例: + 本机重启后,profile 里还是 2.0.12 的旧副本,它的 shim 按自己的 pin 把 bundle 重新供给回 6.7.0, + 覆盖与 6.8.0 一起被抹掉,MCP 行随之 `REA could not start`。 +- **`ghidra-gate-override.mjs` 处理「记录还在、补丁已被 re-provision 抹掉」的半状态**:`--enable` 现在按盘上 + 实际内容判断记录是否仍成立,陈旧记录直接替换而不是报错;`--disable` 对已失去补丁的文件只计数不抛错。 + ### 验证 - 单测 **88 条**(85 过 / 3 平台跳过 / 0 失败);`audit-manifest` **24/24 hash-pinned clean**; diff --git a/README.md b/README.md index 9cf3d13..dc53a41 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ > does **not** move the bundle to the new pins — REA and the engines stay at the versions recorded by the last > setup run. Skip it on a first install and the row exits `MODULE_NOT_FOUND` with no tools and no message; > skip it after an upgrade and you keep running the old REA silently (`check-updates.ps1` reports the drift). +> If you had enabled the Ghidra gate override (see §9 of the 2026-10-26 review), that setup run re-provisions the +> bundled REA and the patch goes with it — re-enable it afterwards, or accept the pinned Ghidra again. > The full rule set is under [Updating](#updating--three-independent-things). > **A first install and every plugin upgrade also show a one-time guide in the Web GUI.** It says whether the diff --git a/README.zh-CN.md b/README.zh-CN.md index 243a606..f8bb512 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -29,7 +29,8 @@ > 市场里的安装/更新只替换包,然后到此为止。从 **2.0.8** 起,市场升级能保证**那一行**继续可用(镜像启动器会 > 转发执行新的那份),但它**不会**把 bundle 换到新的钉子 —— REA 与引擎仍是上一次 setup 时记下的版本。 > 首次安装不跑:行以 `MODULE_NOT_FOUND` 退出,工具消失且没有任何提示;升级后不跑:仍旧跑旧 REA 且没有症状 ->(`check-updates.ps1` 会报出这条漂移)。完整规则见[更新](#更新--三件互相独立的事)。 +>(`check-updates.ps1` 会报出这条漂移)。若你开过 Ghidra 闸门覆盖,这一次 setup 会重新供给内置 REA、覆盖随之 +> 消失 —— 跑完再 `--enable` 恢复,或者接受回到钉住的那一版。完整规则见[更新](#更新--三件互相独立的事)。 > **首次安装与每次升级插件后,Web GUI 会显示一次操作指南。** 它会说明运行时是缺失、还是落后于本插件钉的版本, > 给出这次安装对应的完整 `node "<安装路径>/scripts/setup.mjs"` 命令,并按插件版本记住“已看过”。 diff --git a/client.js b/client.js index de3a207..fd6d23a 100644 --- a/client.js +++ b/client.js @@ -56,6 +56,7 @@ window.__ModuleLoader__.load({ hint: "跑完重启 dsh。这一步只对齐运行时,不会更新插件包本身;升级后同样再跑一次。", noteSmoke: "setup 结束时会用 JDK 自带的 java 跑一次真实分析来验证引擎 —— 那里 30–60 秒没有任何输出是正常的。", noteLock: "若它报 “another provisioning run holds …”,说明后台已有一个 setup 在跑,等它结束再试即可。", + noteOverride: "若你开过 Ghidra 闸门覆盖(让本机跑 REA 未验证的 Ghidra),setup 会重新供给内置 REA,那层覆盖随之消失 —— 跑完执行 node scripts/ghidra-gate-override.mjs --enable --version 12.2 恢复。", }, en: { badge: "ONE-TIME SETUP", @@ -77,6 +78,7 @@ window.__ModuleLoader__.load({ hint: "Restart dsh afterwards. This only aligns the runtime; it does not update the plugin package — run it again after the next upgrade.", noteSmoke: "It finishes by importing a small file to prove the analysis engine works — 30–60 s with no output there is normal.", noteLock: "If it reports “another provisioning run holds …”, a setup is already running in the background; wait for it.", + noteOverride: "If you had enabled the Ghidra gate override (running a Ghidra REA has not verified), setup re-provisions the bundled REA and the patch goes with it — run node scripts/ghidra-gate-override.mjs --enable --version 12.2 afterwards to restore it.", }, }; @@ -354,6 +356,7 @@ window.__ModuleLoader__.load({ }, h("li", null, t.noteSmoke), h("li", null, t.noteLock), + h("li", null, t.noteOverride), ), h( diff --git a/scripts/ghidra-gate-override.mjs b/scripts/ghidra-gate-override.mjs index a288945..5f7dcb8 100644 --- a/scripts/ghidra-gate-override.mjs +++ b/scripts/ghidra-gate-override.mjs @@ -138,11 +138,30 @@ function main() { console.log("no override is recorded; nothing to restore"); return; } + let restored = 0; + let stale = 0; for (const entry of active.files) { - replaceValue(path.join(vendor, entry.file), entry.patched, entry.original); + const file = path.join(vendor, entry.file); + if (!existsSync(file)) { + stale += 1; + continue; + } + const text = readFileSync(file, "utf8"); + if (new RegExp(`${CONSTANT}\\s*=\\s*"${entry.patched.replace(/\./g, "\\.")}"`).test(text)) { + replaceValue(file, entry.patched, entry.original); + restored += 1; + } else if (new RegExp(`${CONSTANT}\\s*=\\s*"${entry.original.replace(/\./g, "\\.")}"`).test(text)) { + stale += 1; + } else { + throw new Error(`${entry.file} declares neither ${entry.patched} nor ${entry.original}; inspect it before restoring`); + } } unlinkSync(record); - console.log(`restored ${CONSTANT} = "${active.originalVersion}" in ${active.files.length} file(s); override record removed`); + console.log( + `restored ${CONSTANT} = "${active.originalVersion}" in ${restored} file(s)` + + (stale > 0 ? `; ${stale} file(s) had already been replaced by a re-provision` : "") + + "; override record removed", + ); return; } @@ -155,7 +174,22 @@ function main() { console.log(`already ${version} — nothing to do`); return; } - if (active) throw new Error(`an override is already recorded (→ ${active.requestedVersion}); run --disable first`); + if (active) { + // Re-provisioning rea-agents replaces the tree, so a record can outlive the patch + // it describes. That stale record must not block the next --enable, and it must + // not be trusted by --disable either: judge it by the files on disk. + const stillApplied = active.files.every((entry) => { + const file = path.join(vendor, entry.file); + if (!existsSync(file)) return false; + const text = readFileSync(file, "utf8"); + return new RegExp(`${CONSTANT}\\s*=\\s*"${entry.patched.replace(/\./g, "\\.")}"`).test(text); + }); + if (stillApplied) { + throw new Error(`an override is already recorded (→ ${active.requestedVersion}); run --disable first`); + } + console.log(`previous record is stale (rea-agents was re-provisioned); replacing it`); + unlinkSync(record); + } const files = []; for (const entry of declarations) {