Skip to content

fix(hide-official): verify the assembled surface without a tool array - #414

Open
WeiLinCool wants to merge 1 commit into
T-Auto:mainfrom
WeiLinCool:fix/413-hide-official-assembly
Open

WeiLinCool wants to merge 1 commit into
T-Auto:mainfrom
WeiLinCool:fix/413-hide-official-assembly

Conversation

@WeiLinCool

@WeiLinCool WeiLinCool commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #413

What was wrong

With dsh-ops-hide-official enabled (replaceTools non-empty) on DSH 0.2.1, every scoped prompt assembly is rejected:

READINESS_BLOCKED (EXECUTOR_UNAVAILABLE): dsh-ops hide-official cannot verify selected
replacements: assembled tool schemas unavailable. ...

The guard read the waterfall payload's tool array unconditionally:

if (!Array.isArray(result?.tools)) throw block(context.scope, 'assembled tool schemas unavailable')
const leaked = result.tools.map(schema => schema.name).filter(hideable)

DSH 0.2.1 (9d0c97b9d2 feat(tools): admit conditional tools per step) transforms a PromptPresentation and attaches tools only after the waterfall returns, so result.tools is always undefined there and the guard can never pass. #387 / 211dc3e fixed the same contract change on the PTC side (hasPtcInstruction, text-only hooks); this is the remaining occurrence.

Minimal reproduction, used as the regression test — a listener that reproduces the host's shape (drop tools before next(), re-attach after, exactly like the host's return { ...transformed, tools }):

ctx.on('system-prompt/assemble', async (assembly, _context, next) => {
  const tools = assembly.tools
  delete assembly.tools
  return { ...await next(), tools }
})

On main@dfafddc that assembly rejects with assembled tool schemas unavailable; with this change it resolves and the selected official tools stay hidden.

The change

  • src/official-tool-hiding.ts: when the payload carries no tool array, resolve the selected official names through the scope view (tools.get(tool, context.scope)) instead of failing on the missing array. A host that still supplies schemas keeps the existing assembled-surface assertion, unchanged.
  • The fallback stays fail-closed: a selected official tool that is still resolvable in the scope is still reported as a leak, with the same message as before.
  • test/lib/official-tool-hiding.mjs: regression that mounts the replacement surface, reproduces the 0.2.1 payload shape, and asserts the assembly succeeds and the visible surface is intact.
  • docs/CHANGELOG.md: ## [Unreleased] entry, English and Chinese.

Deliberately not changed: the collapsed/entry.error interaction above the guard (a collapsed PTC scope still needs an assembled array to be proven), and the PTC-side predicate, which 211dc3e already handles.

Validation

$ npx tsc -p . --noEmit
(no output)

$ node scripts/build.mjs
build: generated PTC declarations for 18 capabilities (113501 bytes)
build: compiled src/ to lib/ and copied 35 assets

$ node test/run.mjs official-tool-hiding
  ok   a host that appends tools after assembly still verifies the visible surface
dsh-ops official-tool-hiding: all 30 checks passed
dsh-ops test: 1 suite(s) passed in 2s

$ npm run check:size
check-file-size: 426 code file(s) checked, all at or under 1000 lines (longest: bench/setup/profile.mjs (927))

$ node scripts/validate-manifest.mjs
validate-manifest: all 133 checks passed

Red without the change, on the same base commit dfafddc with only the new test applied:

  FAIL a host that appends tools after assembly still verifies the visible surface
       Error: READINESS_BLOCKED (EXECUTOR_UNAVAILABLE): dsh-ops hide-official cannot verify
       selected replacements: assembled tool schemas unavailable. ...

Also checked end-to-end against a real DSH 0.2.1-rc.3 AgentLoop with a scripted provider: before, the session's second assembly fails the same way; after, the turn completes. That run is not part of the suite (it needs a 0.2.1 host), so it is evidence only.

$ gh run view 40269009751 --json url,event,conclusion --jq '"\(.url) \(.event) \(.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/40269009751 pull_request success
  • 本 PR 有一次 pull_request 事件触发的 CI required run(上方 URL)
  • npm run verify 相关读数已贴出(本机缺少可用的嵌套 sandbox,PTC 程序执行类用例在本机跳过,故只贴本次涉及套件与门禁脚本的实际输出)
  • 撤掉本次实现后新用例变红(红/绿两端都有记录)
  • docs/CHANGELOG.md 的 ## [Unreleased] 已加中英条目
  • 未新增依赖,未改 package.json

Environment

  • macOS arm64 (darwin 25), Node 24.17.0
  • Base: upstream main at dfafddc
  • Install channel: source checkout, npm ci --ignore-scripts + npm run build

DSH 0.2.1 transforms a PromptPresentation in system-prompt/assemble and
attaches `tools` only after the waterfall returns (9d0c97b9d2, "admit
conditional tools per step"). The assembly guard read that array
unconditionally, so every scoped assembly threw
`assembled tool schemas unavailable` as soon as replaceTools was
non-empty: the whole session was blocked, not one tool.

Resolve the selected official names through the scope view
(`tools.get(name, scope)`) instead, which answers the same question
without the assembled array, and keep the stricter assembled-surface
assertion for hosts that still supply schemas. The fallback stays
fail-closed: a selected official tool that is still resolvable in the
scope is still a leak.

Closes T-Auto#413
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.

[BUG] DSH 0.2.1:hide-official 的组装校验仍假设载荷带 tools,启用该行后每个作用域都被 READINESS_BLOCKED 拦下

1 participant