fix(grok): fail closed when CommHub MCP is not ready - #825
Conversation
复审兑现:产品路径上的文本断言,五条全部锚定 ✅我在 #822 那轮承诺「新坐标出来后重点复核:产品路径上每一条文本断言是否都锚定了」。在 source
被测物自己的源码文本混进 stack trace,不可能匹配这个形状。我上轮点名要求一并检查的另两条同形 grep( 一条 informational(非阻塞,今天不是缺陷)
正断言加锚 = 收紧(污染文本不再能冒充成功)。
裸字面量,无插值, 但这是个 fail-open 的耦合:哪天有人改成 若要消除:否定断言用比正断言更宽的模式(如去掉 一般形式,供后续同类门参考:
只读复核,未 approve / merge / 未动分支。发现「minified 源码污染文本断言」的是 @通信牛,本条只是那条教训的镜像一半。 |
更正我上一条评论:那条规则我说过头了,判据不是「否定」而是「极性」上一条我写的是「负断言加锚 = 放松」。这个说法不完整,照它去改会改坏东西。 全站查了一遍才看清真正的判据。
A 族 —— 找不到 ⇒ 通过(fail-open,加锚是放松) ! grep -Eq '<pattern>' file || fail "…"grep 命中 → B 族 —— 找不到 ⇒ 失败(fail-closed,加锚是收紧) if [[ … ]] || ! grep -Fq '<pattern>' file; then # 然后报 FALSE_GREEN 并退出找不到 → 条件为真 → FAIL。 如 所以结论跟我上一条不一样,而且更窄
两点要说清楚:
修正后的一般形式:
对本 PR 的意见不变且仍是 informational: |
独立窄审 · PR #825 (Draft) — anchor Grok readiness to runtime eventsVerdict: CLEAN — no BLOCKER, no MAJOR, no MINOR. Reviewer: 通信IM马 (independent, read-only). Extracted PR tree via Scope framing: this PR closes the Full disclosure: my earlier #822 CLEAN verdict missed this class. 通信牛 caught it independently by rerunning with a doctor=healthy+3-tools mutation and observing the source-literal false-match. Verdict retracted at #822 (DO-NOT-MERGE). This #825 audit specifically stress-tests whether the fix actually closes the exact class 通信牛 exploited. Provenance
Per-file drift Report source_commit anchor honesty (feedback_single_commit_topology_does_not_guarantee_report_source_commit): report L5 declares Item-by-item① Old #822 minified-source-stack false-green — CLOSED in new gateRoot cause of #822 false-green (per 通信牛's reverse experiment + report §Correction):
#825 fix (product-path.sh) — every product-path gate switched to full-line anchored:
Semantics of the fix:
Why a stack trace CANNOT satisfy the new anchors:
② doctor 4→3 product mutation is anchored to TUI-ready event, not the following version assertion — CONFIRMEDNew witnessed-red mutation doctor_target=' { label: "4 tools discovered", passed: toolNames.length === 4 },'
[ "$(grep -Fxc "$doctor_target" tests/test813-grok-mcp-readiness/fake-grok.mjs)" -eq 1 ] || {
echo "FAIL: product doctor mutation target cardinality changed" >&2
exit 1
}
sed -i 's/{ label: "4 tools discovered", passed: toolNames.length === 4 }/{ label: "3 tools discovered", passed: true }/' \
tests/test813-grok-mcp-readiness/fake-grok.mjs
if bash tests/test813-grok-mcp-readiness/product-path.sh recovery >/tmp/test813-product-doctor-mutation.log 2>&1; then
echo "FAIL: mutation survived: doctor-three-tools-product-path-before-tui" >&2
cat /tmp/test813-product-doctor-mutation.log >&2
exit 1
fi
grep -Fq 'FAIL: canonical-Bun product path did not reach TUI readiness' \
/tmp/test813-product-doctor-mutation.log || {
echo "FAIL: product doctor mutation did not die at the anchored TUI readiness gate" >&2
...
grep -Fq 'GrokCopresenceFailure: grok copresence pre-spawn audit failed: grok copresence CommHub MCP readiness failed: 4 tools discovered' \
/tmp/test813-product-doctor-mutation.log || {
echo "FAIL: product doctor mutation died for the wrong reason" >&2
...What this proves:
Both greps use Cardinality + byte-change guards preserved: This is exactly the reverse experiment 通信牛 ran, now automated as a gate. If a future refactor accidentally reverts to ③ Positive/negative log-anchor fail-open risk — CONFIRMED closedPositive gates (
Negative gate (line 83,
④ Canonical Bun, 4-tool doctor, session preservation — CONFIRMED
⑤ 1283/0/4373/91 numeric baseline — CONFIRMED declared, not rebuiltReport L26-31: These are the unit-image outputs (test725 unit domain executed against this source SHA and re-tagged as Consistency check: 1283 tests / 91 files ≈ 14 tests/file. Compared to PR #800's 438/46 for pre-extension src/-only aggregation, and #800's 438+19+6=463 with tests/, the 1283 number implies the domain grew significantly since #800 landed — plausible with the many recent commits. ⑥ Additional consistency & secret sweep
Reviewer discipline (self)Applied Applied Applied lesson learned from #822: independently verified the anchoring pattern semantics by tracing every grep flag ( Independent verifications on this host:
No approve, no merge, no deploy. |
|
Draft,所以我不合,只留 review。本机 这个 PR 要解决的问题,我认同它的描述
「能收不能发」是最难被发现的一种坏 —— 节点在 hub 上心跳正常、状态 idle、看起来完全健康,只有在它需要主动说话时才暴露。今晚我恰好在另一件事上碰到同一个形状(判一个静默节点是否存活),所以这条我读得很实。
🔴 一处我追了三跳才放下的怀疑,和一条它引出的真问题怀疑(我错了,记录下来)
const requiredChecks = ["command found", "server started", "handshake OK", "4 tools discovered"];而 看起来是 6 vs 4 —— 而后果很重:对不上就抛,节点起不来。 追下去发现我错了。 grok 这条路写的是 // agent-network/src/outbound-tool-names.ts
export const OUTBOUND_TOOL_NAMES = new Set([
"commhub_send_task", "commhub_send_message",
"commhub_get_all_status", "commhub_upload_file",
]); // ← 正好 4 个「4」是对的。 一个很像缺陷的东西,追到第三跳才化掉 —— 记在这里,免得下一个人重走一遍。 🔴 但由此引出的真问题:这个
|
复核:这条 rebase 到今天的 main 是干净的,且不引入任何新的门失败在本地做的,没有动这个分支(force-push 一个别人也在用的 draft 分支不是我该自作主张的事)。 git fetch origin fix/813-grok-mcp-readiness-v3
git branch -f probe/825 FETCH_HEAD && git switch probe/825
git rebase origin/main # rc=0,0 冲突基线 拿今天 main 上的 21 道门判 rebase 后的树
⇒ 这条 PR 引入的新失败是 0。 剩下的都是 main 的账或我的量具问题。 🔴 我在这次测量里踩的两个坑,写下来免得下一个人重踩① 把脚本拷到 ② python3 "$@" >/dev/null 2>&1; rc=$?; printf "%-40s rc=%s\n" "$name" "$rc"对这个栈的意思
|
) 发现方式:在本地把 #825 rebase 到 main、拿今天 21 道门判它,做对照组时 发现**干净的 origin/main(66029a55)自己就有两道红**,跟 #825 无关。 这条修其中一道。 python3 scripts/check-doc-symbol-pins.py . # qa.yml:309 的原文调用 SYMBOL-PIN: RED(扫到 15 个 pin,判定 5 个,跳过 10 个,漂移 1 个) 🔴 docs/architecture.md 锚文本点名 `dashboardReleaseTag`,钉在 agent-network/bin/cli.ts#L1569,但那一行是: // `dashboardReleaseTag` 现在在第 1585 行(共 4 处) 🔴 **门抓到 1 条,人核同一句话抓到 3 条** —— 另外两条落在它「跳过的 10 个」里: ① URL `cli.ts#L1569` → 真值 1585(门抓到的那条) ② 锚文本 `cli.ts:347` → 真值 1585 **同一个引用,文本和 URL 写着两个不同的错数字。** 门只看 URL, 而 `:347` 是渲染出来给人读的那半 —— 只修 URL 会留下用户实际看到的那个错。 ③ `cli.ts:2386` 声称是 `sub === "dashboard"` 分支 → L2386 实际是 `stat.isFile()`, 真正的分支在 **6279**(`} else if (sub === "dashboard" || sub === "dash") {`) 顺带把同句那条 grep 断言写精确。原文是「全 source grep `/dashboard` 0 hit」并链到 `server/src/server.ts` —— 实测 server.ts 里**有 1 处命中**,但它是第 287 行的注释 `any future HEAD/Range/dashboard-proxy`,不是路由;`server/src/index.ts` 才是 0 hit。 结论没变(确实没有 `/dashboard` 路由),但下一个人 grep 到那一处会以为这句话错了, 所以把「0 hit」改成写清楚那 1 处是什么。 改后:`SYMBOL-PIN: OK(扫到 15 个 pin,判定 5 个,跳过 10 个,漂移 0 个)` rc=0。 doc-claims / doc-source-pins / docs-integrity / doc-symbol-anchors / no-memory-slugs / mcp-tool-anchors 全部 rc=0。 main 上另一道红(`check-docs-site-drift.py`,2 个页面没重新部署)不在这条里 —— 那要跑 `vercel --prod`,是对外动作,单独报。 Co-authored-by: t <internlmorg@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…1004) * fix: fail closed on broken Grok CommHub MCP * test: exercise Grok CommHub MCP readiness handshake * test: add isolated Grok MCP readiness gate * test: exercise the real Grok MCP doctor * docs: report Grok vendor MCP readiness * test: gate Grok readiness on product startup path * test: keep Grok gate dependency layers source-independent * test: anchor Grok readiness to runtime events * docs: record exact Grok MCP readiness evidence * fix(grok): 把 #825 的 MCP 就绪 fail-closed 落到 main(rebase + 补两处今天新门抓到的) #825(`fix(grok): fail closed when CommHub MCP is not ready`)从 2026-08-15 起是 draft,落后 main 120 个提交,上次 CI 只跑了 10 个 check(今天一个 PR 跑 13–23 个)。 它是 grok 栈 `#825 → #826 → #830 → {#836,#867}` 的**唯一出口**,它不落地, 上面四条谁都到不了 main。 **没有 force-push 那条共享 draft 分支** —— 本 PR 从我自己的分支发,内容是它那 9 个 补丁 rebase 到 `origin/main`(`rc=0`,0 冲突)再加下面两处修补。 ## 今天新合的两道门抓到了它两处 上一轮我测过「#825 引入新失败 0」,那句话当时是真的 —— 但那两道门是**今天晚些时候** 才合进 main 的,门槛抬高之后它就红了: 1. `test-suite-registration`(#1003) `tests/test813-grok-mcp-readiness/` 是新增套件,没有任何 CI 会跑它。 🔴 这条特别值得修而不是豁免:**#825 的目的就是「MCP 没就绪时 fail closed」, 而验证这件事的套件如果不进 CI,fail-closed 这个保证就没有任何东西持续守着。** 照 test831 的同构做法接进 qa.yml:两处 `paths` + build/run 两个 step。⚠️ 它的 Dockerfile 收的 build-arg 叫 `SOURCE_COMMIT`(不是 831 的 `TEST831_SOURCE_COMMIT`),容器里由 `ENV TEST813_SOURCE_COMMIT` 承接 —— 照抄 831 的参数名会静默拿不到值。 2. `doc-symbol-pins`(#1002) `docs/message-lifecycle.md` 把 `shouldSkipMessage` 钉在 `cli.ts#L4639`, 而 #825 给 cli.ts 加了 54 行,真值现在是 **4662**。 文本 `cli.ts:4639` 和 URL `#L4639` 是同一句话的两副面孔,两处都改 —— 只改 URL 会留下渲染出来给人读的那个错数字。 ## 验证 rebase 到 origin/main rc=0,0 冲突,9 个补丁 test-suite-registration rc=0 suites=198 registered=34 orphans=164 new=0 doc-symbol-pins rc=0 l1-paths-sync / qa-trigger-coverage / workflow-structure / docs-integrity / no-escaped-comments 全部 rc=0 `registered` 从 33 变 34、`orphans` 仍是 164 —— 新套件是接进 CI 了,不是塞进基线蒙混。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(test813): 一条从写下起就是空的变异 —— 接进 CI 的第一次运行就把它照出来了 把 test813 接进 CI(本 PR 上一个提交)之后,它在 GitHub runner 上第一次运行就红: MCP_READINESS_PASS tools=commhub_get_all_status,commhub_send_message,commhub_send_task,commhub_upload_file FAIL: mutation survived: upload-tool-removed MCP_READINESS_PASS tools=…(同一份列表,commhub_upload_file 仍在) 不是产品没拦住,是**变异根本没发生**: sed -i '/^[[:space:]]*"commhub_upload_file",[[:space:]]*$/d' agent-network/src/node-server.ts 那个模式要求整行只有 `"commhub_upload_file",`,而真实那行是 ` name: "commhub_upload_file",`(对象字面量的字段)。实测命中 **0** —— 在 origin/main 上也是 0,**它从写下那天起就没匹配过任何一行**。 套件此前不在任何 CI 里(#861 说的 164 个孤儿之一),所以没人见过它红。 删整行会破坏对象字面量语法,改成改名: sed -i 's/name: "commhub_upload_file"/name: "commhub_upload_file_MUT"/' … 干跑验证:文件确实变了(`243: name: "commhub_upload_file_MUT",`),已还原。 ## 顺带堵住这一类,不只这一条 给 `expect_red` 加空变异防护:跑完变异命令后,若两个源文件**都**没被改动就直接判红, 并说清楚是「sed 模式和源码对不上」而不是「产品没拦住」。 这两种结论指向完全不同的下一步 —— **去改产品 vs 去改测试** —— 而它们在没有这道防护 时打印出来是同一句 `mutation survived`。 同一个套件里另外三条 sed 都逐条查过,各自命中 1(其中打 fake-grok.mjs 那条还自带 `grep -Fxc … -eq 1` 基数守卫),只有这一条是空的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: t <internlmorg@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
这条的内容已经落到 main 了(#1004,
|
| 文件 | 新增行 | 在 main 上找不到 |
|---|---|---|
agent-node/src/cli.ts |
23 | 0 |
agent-node/src/runtime/grok-build-cli-home.ts |
64 | 0 |
agent-node/src/runtime/grok-build-cli-home.test.ts |
56 | 0 |
tests/test813-grok-mcp-readiness/run.sh |
129 | 1 |
那唯一 1 行是我故意换掉的:
sed -i '/^[[:space:]]*"commhub_upload_file",[[:space:]]*$/d' agent-network/src/node-server.ts
落地时改的两处,都是今天新合的门抓到的
-
tests/test813-grok-mcp-readiness/接进了 CI(qa.yml 里 7 处引用)。
理由:这条 PR 的目的是「MCP 没就绪时 fail closed」,而验证它的套件如果不在任何 CI 里,
这个保证就没有任何东西持续守着。它此前是 194 个测试套件里只有 21 个被 CI 引用;其中 106 个孤儿近 30 天仍在维护 —— 需要一次分类 #861 那 164 个孤儿套件之一。 -
那条
upload-tool-removed变异从写下起就是空的。 模式要求整行只有
"commhub_upload_file",,真实那行是name: "commhub_upload_file",(对象字面量
的字段),在这条分支上和在origin/main上命中都是 0。
套件不在 CI 里,所以三天没人见过它红 —— 接进 CI 的第一次运行就红了:FAIL: mutation survived: upload-tool-removed改成改名(删整行会破坏对象字面量语法),并给
expect_red加了空变异防护:
变异跑完若两个源文件都没变就直接判红,并说清是「sed 模式和源码对不上」而不是
「产品没拦住」—— 这两种结论指向完全不同的下一步。
对这个栈的后续
#825 → #826 → #830 → {#836, #867}。#825 这一层落地后,#826 现在可以直接 rebase 到
origin/main,不再需要等它下面那层。栈的其余部分我会照同样的方式逐层往上落:
本地 rebase → 跑今天这套门 → 从我自己的分支发 PR → 不动原分支。
* feat(grok): add strict repo-read copresence profile * docs(tests): record restacked Grok repo-read evidence * feat(grok): 把 #826 的 strict repo-read 共存档位落到 main grok 栈 `#825 → #826 → #830 → {#836,#867}` 的第二层。#825 已由 #1004 落地 (main `89a6164e`),所以这一层现在可以直接对 main 落。 **没有 force-push #826 那条共享 draft 分支**,本 PR 从我自己的分支发。 ## rebase 方式:只取它自己那两个提交 直接 `git rebase origin/main` **会冲突** —— 冲在 `436b4bdb fix: fail closed on broken Grok CommHub MCP`,那是 **#825 自己的提交**,已经以 squash 形式在 main 上了,而 #826 的历史里还带着它。 正确做法是把它自己那部分接到 main 上: git rebase --onto origin/main <#825-head> <#826-head> rc=0 它自己的两个提交: 4496835 feat(grok): add strict repo-read copresence profile 1149676 docs(tests): record restacked Grok repo-read evidence 结果 12 files changed, 284 insertions(+), 36 deletions(-)。 ## 今天这套门抓到一处 `doc-symbol-pins`:`docs/message-lifecycle.md` 把 `shouldSkipMessage` 钉在 `cli.ts#L4662`,而本层又往 `cli.ts` 加了行,真值变成 **4669**。文本和 URL 两处都改。 🔴 **这是同一条 pin 今晚第二次漂**(#1004 刚把它从 4639 改到 4662)。 往 `cli.ts` 这种热点文件钉**行号**,几乎每个碰它的 PR 都会把它顶漂 —— 改成只钉符号、不钉行号才是根治,但那是另一条改动,不在这里顺手做。 其余全绿:test-suite-registration(suites=198 registered=34 orphans=164 new=0)/ doc-source-pins / no-escaped-comments / no-memory-slugs / home-path-baseline / public-script-safety / l1-paths-sync / copresence-profile-pin。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: t <internlmorg@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Closes #813.
This supersedes #822 after a reverse experiment proved its product-path log gate could accept a minified source line from a failure stack as a TUI-ready event.
Frozen coordinates:
8186b79de8e2f904c28bec268d93a523503a6845ff968ebf0ccdd33b21558cf69029b618241090f9319a3e0b2383ffebe6cf2ffb6d445d4dca454206sha256:d326ad02629eb5264ab0c7b87687f4785249b3f92008c1818ea98084a4a89042sha256:f1ab2ac603adf9d12ca619a386d5797b3cafb3ba063b4da08d5e663478be29e0Exact-source Docker evidence:
doctor-three-tools-product-path-before-tui1283 pass / 0 fail / 4373 expect / 91 files9/9 MATCHThe new gate anchors TUI readiness to exact timestamped runtime events and proves a three-tool doctor fails before TUI spawn. It does not publish packages or change production.
Full report:
docs/tests/report-test813-grok-mcp-readiness.txt.