Skip to content

fix(grok): preserve safe TUI composer navigation - #867

Closed
vansin wants to merge 7 commits into
fix/grok-copresence-deny-terminal-toolfrom
fix/grok-copresence-safe-navigation
Closed

fix(grok): preserve safe TUI composer navigation#867
vansin wants to merge 7 commits into
fix/grok-copresence-deny-terminal-toolfrom
fix/grok-copresence-safe-navigation

Conversation

@vansin

@vansin vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What changed

  • preserve safe Left/Right/Home/End/Delete TUI composer editing without weakening the immutable approval-mode boundary
  • return arbitration to idle after a blocked slash command so the next Dashboard task is not stranded
  • retire a terminal co-presence runtime before the next task instead of caching runtime_closed forever
  • bind the inspected CommHub MCP target to the exact pre-resolved Bun executable path
  • correlate batched exact-profile automatic permissions as a bounded multiset, including duplicate tools and out-of-request-order resolutions
  • keep manual overlap, unmatched lifecycle events, overflow, and unresolved terminal completion fail-closed

Root cause

The live 通信狗 incidents had three layers. Its original x-search profile denied a repository tool that Grok nevertheless auto-resolved; the bridge correctly failed closed, but agent-node cached that terminal runtime. The repo-read pilot then exposed an obsolete literal-bun audit comparison. After those were fixed, the real Grok event stream emitted three automatic permission requests first (search_tool, list_dir, search_tool) and resolved them later as (search_tool, search_tool, list_dir). A scalar pending-permission slot misclassified this valid batched lifecycle as an approval-boundary violation, after which six queued tasks failed within seconds.

Frozen coordinates

  • product source: b830403b82a97a98ec004e59c86eb294ddfd1771
  • report-only head: 19af299adea89cb6296a5d3971b574ad88e2c423
  • exact candidate image: sha256:261ade0fd44147370df56a7377f409c840f3e65a07aa472174e7761184b45ecb
  • candidate agent-node bundle SHA-256: 5c0d5cbcfb0350af1eb27e78de1ccd5902c5ba22be732df415fe26851c269a45

This Draft remains stacked on the existing Grok co-presence work. It does not publish npm or merge into the base branch.

Validation

  • the new batched-permission fixture was witnessed red on the previous runtime with duplicate preview automatic permission request
  • exact-source focused Docker runtime/profile/near-miss layer: 84 pass / 0 fail / 3 files
  • exact-source full test725 domain: 1288 pass / 0 fail / 92 files; named mutation red; RESULT: PASS
  • exact source built successfully into the candidate runtime and produced the bundle SHA above
  • single-node pilot retained exact tmux session 通信狗 with 0:node and 1:tui; it reached TUI input-ready, CommHub registration, and SSE, and Hub reports idle with zero pending/in-flight tasks
  • no production task was synthesized, retried, or sent during recovery

Full evidence: docs/tests/report-grok-copresence-fatal-recovery-7914755a.txt.

Honest limits

  • owner-origin Dashboard behavior remains the final UAT; task traffic stays paused until that explicit test
  • /model remains intentionally blocked: the current FIFO protocol cannot yet prove when Grok's interactive model picker has returned to the composer. Persisted model changes still require a controlled config update and restart.

@vansin

vansin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Draft,不合,只留 review。这个 PR 改的是 isGrokPreviewAutomaticResolution —— 决定「这次自动放行算不算被认可的例外」的那个谓词。安全相关,所以我逐条读了。

🔴 一、那条身份比对,在两个分支下分别是恒假恒真

改后的判据里有这一条:

&& requestId === `tool:${input.requestTool}`

其中两个值都来自同一个 event:

const requestId = lifecyclePermissionIdentity(event);   // :3286
const eventTool = lifecyclePermissionTool(event);       // :2122
 isGrokPreviewAutomaticResolution({ requestTool: eventTool,})

lifecyclePermissionIdentity 是这样的:

if (requestId /* event.request_id | event.requestId */) return `id:${requestId}`;
return toolName ? `tool:${toolName}` : null;

分两种情况:

event 带 request_id requestId tool:${requestTool}
id:… 永远不相等 ⇒ 谓词恒假
不带 tool:<tool_name> requestTool 就是同一个 tool_name恒真

也就是说:这一条在能通过的那个分支里,比较的是同一个 event 里的同一个字段和它自己。它贡献 0。

🔴 而它看起来像是在做「把这次 resolution 和当初那个 request 对上」。 这是一条读起来像身份校验、实际不做校验的断言 —— 它的危险不在于错,在于它占住了那个位置,让后来的人以为那里有一道校验。

二、被它替换掉的那条,是真的身份绑定

改之前:

-    && input.activeRequestId === `tool:${input.requestTool}`
-    && requestId === input.activeRequestId

而当时 requestTool 传的是 this.activePermissionExactPreviewTool(跟踪状态),这次改成了 eventTool(事件自带)(:669-671)。

⇒ 旧版第二个合取项 requestId(来自 event) === activeRequestId(来自跟踪状态) 是一次真的 event ↔ state 交叉校验

新版把它换成了 pendingRequestCount > 0 —— 一个计数。

🔴 计数不能回答「这次 resolution 属于哪一个 request」。 而这个 PR 的动机恰恰是并发/重叠的 request(测试名:exact search_tool then use_tool in a human turn)—— 在重叠场景下,「有请求挂着」和「挂着的正是这一个」的区别最大,而新版只保留了前者。

我理解旧版单个 activeRequestId 确实建模不了重叠 —— 但出路应该是把它升级成一个「挂起中的 id 集合」并按 id 匹配,而不是降级成计数。 现在 pendingAutomaticPermissions 已经是一个 Map<tool, count>;把它做成 Map<tool, Set<requestId>> 的改动量不大,而且能把上面那条恒真/恒假的比较变成一条真的校验。

三、waitingHuman 的极性被反了,想确认这是有意的

-    && input.waitingHuman
+    && !input.waitingHuman

注释给了理由(process 级 --always-approve 属于共享的 grok 进程、不属于 anet 的逻辑 turn owner,所以人类 turn 也会产生同样的自动 resolution 生命周期)。

但这不是放宽,是换了一边:旧版要求「正在等人」,新版要求「不在等人」。原来被这条接住的那一类(确实在等人时到达的自动 resolution),现在会被判成不是认可的例外。

⚠️ 想确认:那一类现在走哪条路? 如果落到 fail-closed 的分支上,那是安全的(只是更严);如果落到别的分支,需要说明。极性翻转在一个安全谓词里,值得在注释里单独写一句「翻转之后,原来那一侧现在的归宿是 X」 —— 否则下一个读的人只能看到「有理由」,看不到「另一侧去哪了」。

四、值得肯定的

runtime-retirement.ts(+29/-0)带了自己的测试(+62),而且 report-grok-copresence-safe-navigation-mutation-*.txt单独的变异报告 —— 把「跑绿了」和「变异能打红」分成两份证据存,这个习惯是对的。


总结:①那条身份比对建议要么改成真的按 id 匹配、要么删掉(留着比删掉更坏,因为它占位);②pendingRequestCount 建议升级回按 id 的集合;③极性翻转补一句「另一侧的归宿」。三条都不阻塞你继续往下做,但①我认为该在这个 PR 里解决 —— 它是这次改动引入的,不是既有债。

vansin added a commit that referenced this pull request Aug 18, 2026
…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>
vansin added a commit that referenced this pull request Aug 18, 2026
* 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>
vansin pushed a commit that referenced this pull request Aug 18, 2026
预跑今天这套门时,#867 这一层红了三条。三条都是真的,逐条修,没有走豁免。

## 🔴 1. 能力边界回归(copresence-profile-pin)

    ::error file=agent-node/src/cli.ts,line=76::static import of grok-copresence/policy|runtime.
    ESM static imports are hoisted, so its top level runs BEFORE the profile pin at line 570

#867 加了一条**静态** import:

    import { retireStoppedGrokCopresenceRuntime } from "./runtime/grok-copresence/runtime-retirement";

ESM 静态 import 是提升的 —— 被导入模块的顶层会在 cli.ts 第一条语句之前执行,于是
`policy.ts` 顶层那句 `readPinnedGrokCopresenceCapabilityProfile()` 读到的是 **ambient
环境变量**,而不是启动时从节点配置钉下来的档位。**能力档位就此可以被环境变量绕过。**

改成动态 import,放在调用点、启动钉档位之后,与本文件里另外两处 grok-copresence
的取法一致:`static_imports 1 → 0,dynamic_imports 2 → 3`。

这道门(#937 建的)第一次抓到真东西,而它抓到的是一条**在 draft 里躺了三天**的回归。

## 2. 公开仓里的真人 home 路径(home-path-baseline)

新文件 `docs/tests/report-grok-copresence-fatal-recovery-7914755a.txt` 有 3 处
`/home/<真人名>/`。承重的是 `.commhub/runtime-commdog-…` 那部分,前缀不是,
所以按门给的第一条修法换成 `~/`,证据一字不损,**不抬基线**。

## 3. 行号 pin 第三次漂(doc-symbol-pins)

`docs/message-lifecycle.md` 的 `shouldSkipMessage`:本层又动了 `cli.ts`(含上面第 1 条
的改动),`4669 → 4695`。文本和 URL 两处都改。

(这是它今晚第三次漂:4639 → 4662 → 4669 → 4695。我曾提议「去掉行号根治」,
**那句已撤回** —— 实测去掉 `#L` 会让这条 pin 整个掉出那道门的正则:
`扫到 15→14 / 判定 5→4 / rc 1→0`,是删覆盖不是修问题。)

## 复跑

    home-path-baseline / copresence-profile-pin / doc-symbol-pins / test-suite-registration /
    doc-source-pins / no-escaped-comments / no-memory-slugs / l1-paths-sync    全部 rc=0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
vansin added a commit that referenced this pull request Aug 18, 2026
* fix(grok): preserve safe TUI composer navigation

* test(grok): record safe navigation evidence

* fix(grok): recover after fatal copresence boundary

* docs(test): record communication dog recovery evidence

* fix(grok): correlate batched automatic permissions

* docs(test): record batched approval recovery

* fix(grok): 落地前修掉 #867 的三处 —— 其中一处是能力边界回归

预跑今天这套门时,#867 这一层红了三条。三条都是真的,逐条修,没有走豁免。

## 🔴 1. 能力边界回归(copresence-profile-pin)

    ::error file=agent-node/src/cli.ts,line=76::static import of grok-copresence/policy|runtime.
    ESM static imports are hoisted, so its top level runs BEFORE the profile pin at line 570

#867 加了一条**静态** import:

    import { retireStoppedGrokCopresenceRuntime } from "./runtime/grok-copresence/runtime-retirement";

ESM 静态 import 是提升的 —— 被导入模块的顶层会在 cli.ts 第一条语句之前执行,于是
`policy.ts` 顶层那句 `readPinnedGrokCopresenceCapabilityProfile()` 读到的是 **ambient
环境变量**,而不是启动时从节点配置钉下来的档位。**能力档位就此可以被环境变量绕过。**

改成动态 import,放在调用点、启动钉档位之后,与本文件里另外两处 grok-copresence
的取法一致:`static_imports 1 → 0,dynamic_imports 2 → 3`。

这道门(#937 建的)第一次抓到真东西,而它抓到的是一条**在 draft 里躺了三天**的回归。

## 2. 公开仓里的真人 home 路径(home-path-baseline)

新文件 `docs/tests/report-grok-copresence-fatal-recovery-7914755a.txt` 有 3 处
`/home/<真人名>/`。承重的是 `.commhub/runtime-commdog-…` 那部分,前缀不是,
所以按门给的第一条修法换成 `~/`,证据一字不损,**不抬基线**。

## 3. 行号 pin 第三次漂(doc-symbol-pins)

`docs/message-lifecycle.md` 的 `shouldSkipMessage`:本层又动了 `cli.ts`(含上面第 1 条
的改动),`4669 → 4695`。文本和 URL 两处都改。

(这是它今晚第三次漂:4639 → 4662 → 4669 → 4695。我曾提议「去掉行号根治」,
**那句已撤回** —— 实测去掉 `#L` 会让这条 pin 整个掉出那道门的正则:
`扫到 15→14 / 判定 5→4 / rc 1→0`,是删覆盖不是修问题。)

## 复跑

    home-path-baseline / copresence-profile-pin / doc-symbol-pins / test-suite-registration /
    doc-source-pins / no-escaped-comments / no-memory-slugs / l1-paths-sync    全部 rc=0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(grok): 摘掉 `7914755a` —— 它让 test813 的 canonical-Bun 产品路径起不来

CI 上 `doc source-pin floor (Docker)` 这个 job 红了(它同时跑 test813):

    MUTATION_RED upload-tool-removed          ← 我在 #1004 修好的那条空变异,现在真的会红了
    MUTATION_RED stale-three-tool-doctor
    PRODUCT_PATH_NEGATIVE_PASS registration=absent session=unchanged
    FAIL: canonical-Bun product path did not reach TUI readiness
    GrokCopresenceFailure: grok copresence pre-spawn audit failed:
      grok copresence refuses any MCP server except its runtime-owned commhub stdio server
      failureCode: 'spawn_audit'

## 为什么

`7914755a fix(grok): bind MCP audit to resolved Bun path` 把审计从

    commhubRecord.target !== "bun"

改成把**解析后的绝对 bun 路径**传进去、两侧 `resolve()` 后再比。而生成那份 MCP 配置的
`resolveGrokCommhubMcpCommand()` 用的是 **`realpathSync()`(解析符号链接)**,
审计侧用的是 **`resolve()`(只归一化路径,不解析符号链接)**。容器里 `bun` 是符号链接时,
两侧就对不上 —— 于是 pre-spawn 审计把自己生成的那份配置也拒了。

## 处理:只摘这一个提交,其余照落

    git rebase --onto <该提交>^ <该提交> <分支>

剩下 7 个提交,`runtime.ts` 那处比较回到 main 现在的写法(`target !== "bun"`)。
**#883 那条 P0 点名的 `b830403b` 和 #881 的修复 `a9df8dba` 都在保留的这 7 个里**,
本次落地的核心价值不受影响。

## 不在这里顺手修 `7914755a` 的理由

它要解决的问题是真的(裸 `"bun"` 比较挡不住指向别处的 bun)。但把它改对需要决定
**审计与生成两侧统一用哪一种解析**(`realpath` vs `resolve`),那是安全审计语义,
要有自己的见红证据;而且它已经被 #836 的核对牵扯进来(`fc221fc5` 是它的前身)。
**留作单独一条,不塞进这条含 P0 的 PR。**

## 顺带

摘掉这 6 行之后 `shouldSkipMessage` 的行号 pin 从 4695 变回 **4691**,已改(文本+URL)。
八道门在本地全 rc=0。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: t <t@t>
Co-authored-by: t <internlmorg@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@vansin

vansin commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

这条的内容已落到 main(#1010f1208449),因此关闭

没有 force-push 这条分支。 落地方式:git rebase --onto origin/main <#830-head> <#867-head>
再从我自己的分支发 PR。grok 栈四层至此全部落地:

#825  #1004 → main 89a6164e   已关闭
#826  #1006 → main c85dca96   已关闭
#830  #1008 → main 526163cd   已关闭
#867  #1010 → main f1208449   本条

按内容验,并逐条解释每一处差异

文件 新增行 在 main 上找不到
agent-node/src/runtime/grok-copresence/runtime.ts 136 2
agent-node/src/runtime/grok-copresence/runtime.test.ts 120 5
agent-node/src/runtime/grok-copresence/runtime-retirement.ts 27 0
agent-node/src/runtime/grok-copresence/runtime-retirement.test.ts 58 0
agent-node/src/cli.ts 19 5
docs/tests/report-grok-copresence-safe-navigation-a9df8dba.txt 356 0
docs/tests/report-grok-copresence-fatal-recovery-7914755a.txt 102 3
其余 3 个文件 0

每一处「找不到」都是我有意为之的,逐条列出来:

cli.ts (5)
  import { retireStoppedGrokCopresenceRuntime } from "./runtime/grok-copresence/runtime-retirement";
     → 改成了动态 import(见下「能力边界回归」)
  assertGrokCopresenceApprovalOwnership( / inspection, / auditRuntime.grokCliHome.home, / commhubMcpCommand,
     → 来自被摘掉的 7914755a

runtime.ts (2)
  expectedCommhubCommand = "bun",
  || resolve(String(commhubRecord.target || "")) !== resolve(expectedCommhubCommand)
     → 来自被摘掉的 7914755a

report-…-fatal-recovery (3)
  previous runtime: /home/vansin/.commhub/…   ×3
     → 按 home-path-baseline 那道门的要求脱敏成 ~/,证据部分一字未动

没有一处无法解释的丢失。

落地时改了三处,两处是它自带的问题

1. 🔴 能力边界回归(copresence-profile-pin 抓到)

本分支加了一条静态 import:

import { retireStoppedGrokCopresenceRuntime } from "./runtime/grok-copresence/runtime-retirement";

ESM 静态 import 是提升的 —— 被导入模块的顶层在 cli.ts 第一条语句之前执行,
于是 policy.ts:22readPinnedGrokCopresenceCapabilityProfile() 读到的是
ambient 环境变量,而不是启动时从节点配置钉下来的档位。能力档位就此可被环境变量绕过。
改成动态 import 放在调用点(钉档位之后):static_imports 1 → 0,dynamic_imports 2 → 3

顺带查「同样的危害还有没有别的路径」,发现那道门本身有个可绕过的缺口
(传递静态 import),已带实测假绿开在 #1009,未在本次顺手改。

2. 摘掉 7914755a(CI 上真红)

doc source-pin floor (Docker) 红了:

FAIL: canonical-Bun product path did not reach TUI readiness
GrokCopresenceFailure: grok copresence pre-spawn audit failed:
  grok copresence refuses any MCP server except its runtime-owned commhub stdio server
  failureCode: 'spawn_audit'

根因:生成配置那侧用 realpathSync()(解析符号链接),7914755a 改后的审计侧用
resolve()(不解析)。容器里 bun 是符号链接 ⇒ 节点拒绝了自己刚生成的配置
外科式只摘这一个提交,其余 7 个照落。它要解决的问题是真的(裸 "bun" 比较挡不住
指向别处的 bun),已带证据开在 #1011

3. 公开仓真人 home 路径 + 行号 pin

见上表。

这条修了什么(已按内容核过,不是看标题猜的)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant