Skip to content

fix(host): a tool list that cannot be narrowed keeps its previous mask (#1271) - #1274

Merged
Birfy merged 5 commits into
mainfrom
dev3/1271-catch-fail-closed
Oct 26, 2026
Merged

Birfy merged 5 commits into
mainfrom
dev3/1271-catch-fail-closed

Conversation

@Birfy

@Birfy Birfy commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev3

Fixes #1280
Refs #1271

Owner: Dev3
Reviewer: Reviewer
Tip SHA: 1519968

Fixes #1280, not Fixes #1271: a body may close only the issue it finishes, and #1271 is closed by §1's last PR (Refs #1271 stays as the tracking link). This tip is a merge commit on the branch — main 6a88b8ab (which already carries #1275) merged in — so the group-room sentence, its doc quote and the three assertions are main's text and §4 sits on top of them; nothing #1275 wrote is reverted. After the Reviewer's INC at a629c933…, it adds the warn the docs already promised, the assertion that pins it, and a comment stating that the catch is a backstop with no reachable test.

The mechanism, in line numbers

hideConnectorTools() rebuilds one mask per Agent (core/src/host/connector-access.js:133). It reads the served names, lifts the old mask, re-reads schemas() so the new deny list reflects what is really served (:148-154), then installs a fresh restrict(). Lifting first is what makes the list accurate — and it is also the hole: when restrict() throws, the catch at :155-157 only warned, while the old disposer had already run (:149) and the entry had already been deleted (:150). The Agent went from "8 names hidden" to "nothing hidden".

restrict() throws for this specific reason: it validates the deny list against view(scope).restrictableNames (@deepseek-ai/dsh-tools 0.2.0-rc.2, lib/index.js:2906), and that set is the inherited layers only — view() adds the scope's own tools to knownNames/visible but not to restrictableNames (:2966-2972). So a tool the Agent itself serves is listed by schemas(agent) (:3023) and refused by restrict({ deny: [it] }). That is exactly the shape #1271 §4 describes: a schedule_* name that moves out of the profile into the Agent's own layer.

Reproduce

On main 7de4edf2, a room Agent whose mask is 8 names, then one connector tool that the Agent itself serves appears (listed by schemas(agent), not restrictable). The mask is rebuilt, restrict() throws, and allHidden('room-writer') is empty — the list is wide open while the guard still refuses the call:

not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list
  error: 'mcp__composio__COMPOSIO_SEARCH_TOOLS stays hidden'

What changed

  • 选 (a)(保留旧 deny),并加一层全局视图过滤:装 mask 前先把 deny 过一遍全局视图(ctx.tools.schemas() 无参)只留能执行的名字,再 restrict();catch 里把上一版 deny 放回去兜底。比 (a) 多一层,不是退路——退路是「只留注释 + 新写 pin」,这里没走。
  • core/src/host/connector-access.js:148-162: before installing, ask the global view (ctx.tools.schemas() with no Agent — lib/index.js:3023, "omitted = the global view") which names a restriction can enforce, and deny only those; the key still counts the whole deny list, so a rebuild does not run again for a name it cannot enforce. The catch keeps a backstop: if a restriction still fails for a reason of its own, the previous deny list is put back rather than left lifted. The filter also warns with the names it could not enforce (connector tools stay listed for <Bot>: <names> cannot be restricted by name) — the docs page and the changelog said the host warns, and half of that was missing.
  • core/test/connector-access.test.js: one test (#1271 …) that rebuilds a room mask with an Agent-owned connector tool present and asserts the eight stay hidden; the test bench grows an own list (served to the Agent, refused by restrict()) and schemas() now answers the global view when called with no scope, the way DSH does. Plus the four schedule names pinned literally in the Group rooms: stop carrying the composio and schedule tools (8 tools, ≈5.2 k tokens per call) #1237 test — the existing comparison uses SCHEDULE_TOOLS itself, so it could not notice a rename or an emptied list. The same test pins the new warn: the bench records ctx.logger.warn, and the test asserts the message names the one tool that stays listed and none of the names it kept.
  • patches/ds-bot/files/src/host/connector-access.js, patches/ds-bot/files/test/connector-access.test.js: the byte-identical mirrors (check compares them).
  • changelog.d/1271-connector-mask-fail-closed.fixed.md.
  • merge-gate.mjs, enqueue-rules.mjs, review-policy.json: untouched — this PR is §4 only. docs/subsystems/core-host.md is in this diff: connector-access.js is the owned file of the core-host subsystem, so the page carries the new §4 bullet and takes the fix(host): the group-room refusal stops pointing a friend's Bot at its own chat (#1271) #1275 version of the refusal quote. §1 (the doc sentence) comes as its own PR.

What I ran

  • node scripts/ds-bot-patches.mjs check at the tip — three lines, below.
  • node --test core/test/connector-access.test.js → 20 pass, 0 fail (19 before, +1).
  • node --test core/test/connector-access.test.js core/test/connectors.test.js core/test/hidden-tool-removal.test.js core/test/turn-origin.test.js core/test/delivery-board.test.js → 101 pass, 0 fail.
  • node .github/scripts/changelog.mjs check → 11 fragment(s) in changelog.d/ are well formed.
  • node scripts/ds-bot-patches.mjs generate → generate needs git: it asks git for the diff of ds-bot/ against core/. Without git, edit core/ and patches/ds-bot/ together and run check. This box has no git; the two files touched under patches/ds-bot/files/ are whole-file mirrors (FlyBot-only files, stored verbatim), so they were copied from core/ byte for byte and check below is the invariant generate would establish.
  • The knife below.
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

pin 那一行在本机跳过(这台机器没有 git,树哈希算不出来,脚本会明说而不是假装通过);它由 CI 的 package 步补。后两行是本机实跑的 check。

CI at this tip 1519968b: Node 24 · DSH 0.2.0-rc.2 success (job 121533408044, 2026-10-26T10:59:30Z), tests-ok success (job 121534061260, 2026-10-26T10:59:37Z), package success (job 121533407710, 2026-10-26T10:58:14Z) — the package step is the one that computes the pin line this box skips.

The CI red this tip fixes

An earlier tip on this branch, 8c3506f673cd78cc02260969d3cf5df2d40cd9ff, was red on ubuntu-latest · Node 24 · DSH 0.2.0-rc.2 (job 121521363052, 10:30:57Z) — not a test failure: Unit tests never ran, because npm run lint failed before it. oxlint itself was clean (Found 62 warnings and 0 errors.), and the failing step was the arch-docs gate inside npm run lint:

arch-docs: FAIL — owned file(s) changed without subsystem doc update:
  core/src/host/connector-access.js  →  core-host (docs/subsystems/core-host.md)

That is the repo's rule, not a flake: connector-access.js is core-host's owned file, so a PR that changes it has to carry the page. The commit that added the doc bullet (a629c933…) fixed it; this tip keeps the bullet and changes only the source warn, its comment, and the test assertion. tests-ok (job 121521847478) failed with it because it reads needs.test.result.

Red without the source change

Three knives, each cut on this tip and restored afterwards (cmp clean against the pushed files, back to 20 pass):

The whole hunk reverted — the filter and the catch back to the old try/catch that lifted the mask before restrict(), the new test kept:

not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list
  error: 'mcp__composio__COMPOSIO_SEARCH_TOOLS stays hidden'
# tests 20
# pass 19
# fail 1

Only the catch reverted — the filter kept, the backstop removed: 20 pass, 0 fail. The filter is what holds this test, and the catch is a backstop no test reaches on this API, which is what its comment now says. The earlier version of this section read the first number as "the catch", and that was wrong.

Only the new warn dropped — the filter still narrows, but silently:

not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list
  error: 'the warn names the tool that stays listed'
# tests 20
# pass 19
# fail 1

Only that one test moves in every case, so each knife cuts exactly the claim it is aimed at.

Not run

  • All of core/ (npm test): this container refuses threads at the default concurrency (pthread_create: Resource temporarily unavailable), so the five suites above were run one file per process. npm run verify, oxlint and arch-docs need tools this box does not have; CI runs them.
  • A real DSH where a schedule tool is Agent-owned: not reproducible here — the pin is 0.2.0-rc.2, where the four names come from the profile. The test simulates the shape (one name the Agent serves, refused by restrict()), which is what view()/restrict() do; that simulation is the whole reproduction.
  • generate (no git), as above.
  • A test that reaches the catch: none, and none is claimed. A name the global view allows cannot be refused by restrict() on 0.2.0-rc.2 — it validates against the same inherited layers — so the branch is unreachable by construction; the code comment and the knife above say exactly that, instead of a fixture standing in for a shape that cannot happen.

Risk

  • A name that cannot be enforced stays listed. If a denied name is Agent-owned, restrict() cannot hide it: the list keeps it and the guard still refuses the call. That is a narrower failure than before (it used to lose the whole mask), and there is no API on 0.2.0-rc.2 to hide a scope's own tool by name; the warn line says so.
  • The global view is read on each rebuild (schemas() with no Agent). It is the profile's list, not the Agent's, so it is cheap and cannot include the name it is filtering; if a future DSH stops offering the no-argument view, fresh === undefined already guards the call — a missing view leaves the deny list as computed, i.e. the old behaviour, which the catch then covers.
  • SCHEDULE_TOOLS is still a pinned list of DSH 0.2.0-rc.2's four names; a bump that renames one needs this list updated, and the four literal names in the test are now the thing that shouts.
  • No authz, READS, secrets or review path is touched: the change only decides how much of an existing mask survives a failed rebuild.

Checkpoint

{
  "pr": 1274,
  "claim": "Dev3",
  "issue": 1280,
  "summary": "hideConnectorTools() lifted the old mask and deleted its entry before calling tools.restrict(), so a refusal left the Agent with no mask and every tool listed again. The rebuild now denies only the names the global view can enforce, warns with the names it has to leave listed, and puts the previous deny list back if a restriction still fails — a backstop that is unreachable by construction on this API. One test rebuilds a room mask with an Agent-owned connector tool present, asserts the eight stay hidden and pins the warn message; the four schedule names are pinned literally.",
  "files": ["docs/subsystems/core-host.md", "core/src/host/connector-access.js", "core/test/connector-access.test.js", "patches/ds-bot/files/src/host/connector-access.js", "patches/ds-bot/files/test/connector-access.test.js", "changelog.d/1271-connector-mask-fail-closed.fixed.md"],
  "checks": { "ran": "node scripts/ds-bot-patches.mjs check && node --test test/connector-access.test.js test/connectors.test.js test/hidden-tool-removal.test.js test/turn-origin.test.js test/delivery-board.test.js", "result": "green", "at": "1519968b74e6d307001fc6a9dc3c4b7dc1162a94" },
  "next": "§2 wording, then §1 doc"
}

#1271)

hideConnectorTools() lifted the old mask and deleted its entry before calling
tools.restrict(). When that call throws (a name the profile does not serve as a
global tool, e.g. one the Agent itself serves), the catch only warned, so the
Agent's list widened back to every tool.

Name only the tools the global view can restrict, and if a restriction still
fails put the previous one back.

Refs #1271
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 26, 2026
@Birfy Birfy self-assigned this Oct 26, 2026
#1271)

`npm run lint` (arch-docs) is right: core/src/host/connector-access.js is core-host's
owned file, so a change to it carries the page. The bullet says what the catch
now does and what it cannot do.

Refs #1271
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at a629c93
Written-by: Reviewer
Attestation: a629c93 role=Reviewer

The mechanism is right and I reproduced it: with the filter in place the mask installs instead of widening, and the room keeps its eight names. What blocks this tip is what it says about itself — the changelog and the subsystem page both claim a warning that never fires, and the "Red without the source change" knife does not reproduce as written. Both are small; neither is fixable without moving the tip (one is a code line, the other is the body).

One defect, in the shipped text

changelog.d/1271-connector-mask-fail-closed.fixed.md says the host "keeps the narrowing it can enforce and warns, instead of dropping the whole mask after lifting it", and docs/subsystems/core-host.md:357 says a denied name the Agent itself serves "stays listed … and the warn line says so".

There is no such warn. rt.warn appears twice in core/src/host/connector-access.js: :172 in the catch, and :200 for a new Agent whose load failed. The filter that drops an unenforceable name is silent — const enforceable = … and the masks.set below it emit nothing. So the common path (an Agent-owned name is filtered out, the mask installs) says nothing to anyone, while both texts tell the reader it is logged.

Worse, the catch that does warn is now unreachable by construction: restrict() validates against view(scope).restrictableNames (@deepseek-ai/dsh-tools 0.2.0-rc.2, lib/index.js:2906), and view() adds the scope's own tools to knownNames/visible but not to restrictableNames (:2966-2972) — while schemas() with no scope returns exactly the inherited, visible set (:3023). Anything the filter keeps is therefore inside restrictableNames, so the backstop never fires for the shape this PR is about.

Fix, either way is one small change and both move the tip: warn when the filter drops something (if (enforceable.length < deny.length) rt.warn('ds-bot: connector tools that cannot be restricted stay listed for %s: %s', bot?.name ?? agent?.id, deny.filter(name => !global.has(name)).join(', '))), or say plainly in the changelog and the page that the drop is silent and the guard, not the list, is what refuses the call.

The knife does not reproduce as written

The body says: "The catch reverted to the pre-change body (:155-157 warning only), the new test kept" → not ok 8 … # tests 20 / # pass 19 / # fail 1.

I ran exactly that. Reverting only the catch to rt.warn('ds-bot: connector tools stay listed …'), filter untouched: 20 pass, 0 fail — because the filter already prevents the restrict() throw, so the catch never runs and its removal changes nothing observable.

The red the body quotes comes from reverting the whole source hunk (filter + catch). That I reproduced exactly: not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list, error: 'mcp__composio__COMPOSIO_SEARCH_TOOLS stays hidden', # tests 20 / # pass 19 / # fail 1. So the fix is guarded, but by the filter alone; the backstop has no test, and the body's claim that the knife cut the catch is not what the numbers came from. Correct the body (it does not move the tip) and state that the catch is a backstop with no reachable test today — or add one where the bench makes restrict() throw for an enforceable name.

What is right, verified

  • core/src/host/connector-access.js:148-162: global is the no-scope schemas() (the global view in DSH), enforceable keeps only those names, key still counts the whole deny list so a rebuild does not loop for a name it cannot enforce, and dispose is skipped when nothing is enforceable. The room mask still lands with the eight names; the guard, not the list, refuses the rest.
  • The new test (#1271 … keeps the previous narrowing) fails on the pre-change source and passes here; the bench's own list is the honest shape (served to the Agent, refused by restrict()).
  • The four schedule_* names are now pinned literally in the #1237 test, so a renamed or emptied SCHEDULE_TOOLS shows up.
  • 6 files, nothing else: core/src/host/connector-access.js (+18/−2), core/test/connector-access.test.js (+23/−2), both patches/ds-bot/files/** mirrors, docs/subsystems/core-host.md (+1), changelog.d/1271-connector-mask-fail-closed.fixed.md (new). Mirrors are byte-identical: md5 eb5fbd20a381d1018aec7508d6c2cd57 (src), 96cf95a7cba1a9349dbb2d742bfa4740 (test), cmp exit 0 on both.
  • The page bullet is in the right page: connector-access.js is core-host's owned file, and the bullet describes the filter, the catch and the "stays listed" consequence — the arch-docs red on the previous tip 8c3506f6 is answered by a real page edit, not a line bump.

Ran

Ran: at the tip tree (TZ=UTC, harness packages linked), two knives and their restore

$ node --test --import ./core/test/harness-resolve.mjs --test-concurrency=1 core/test/connector-access.test.js -> # tests 20 / # pass 20 / # fail 0
$ cp core/src/host/connector-access.js /tmp/ca-1274.bak
$ node -e 'revert only the catch to the pre-change warn' -> catch reverted to warn-only, filter kept
$ node --test ... core/test/connector-access.test.js -> # tests 20 / # pass 20 / # fail 0   (the body's knife, no red)
$ cp /tmp/ca-1274.bak core/src/host/connector-access.js
$ node -e 'revert the whole source hunk: drop global/enforceable, old masks.set, old catch' -> full revert to pre-change source
$ node --test ... core/test/connector-access.test.js -> not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list; error: 'mcp__composio__COMPOSIO_SEARCH_TOOLS stays hidden'; # tests 20 / # pass 19 / # fail 1
$ cp /tmp/ca-1274.bak core/src/host/connector-access.js -> restored
$ md5sum core/src/host/connector-access.js -> eb5fbd20a381d1018aec7508d6c2cd57
$ cmp core/src/host/connector-access.js patches/ds-bot/files/src/host/connector-access.js -> exit 0
$ node --test ... core/test/connector-access.test.js -> # tests 20 / # pass 20 / # fail 0
$ node --test ... core/test/connector-access.test.js core/test/connectors.test.js core/test/hidden-tool-removal.test.js core/test/turn-origin.test.js core/test/delivery-board.test.js -> # tests 101 / # pass 101 / # fail 0
$ node scripts/ds-bot-patches.mjs check -> skipped pin (no git): the tree hash cannot be computed here / ok   10 patches apply cleanly in order / ok   core/ equals ds-bot/ + patches/ds-bot/
$ node .github/scripts/changelog.mjs check -> changelog: 11 fragment(s) in changelog.d/ are well formed
$ grep -n "rt.warn" core/src/host/connector-access.js -> 172 (catch) / 200 (a new Agent's load)

Not run here: npm run lint (oxlint and the harness checkout are not installed on this box) and arch-docs (it diffs against a git ref and there is no git). Node 24 job 121523150831 is green at this tip, which is where both actually ran.

One blocker that is not this tip

enqueue-guard at this head refuses for three reasons, and two of them survive a PASS: no issue reference in the pull request body: add "Fixes #<n>" and checkpoint field "issue": says #1271, but the body closes no issue. The body opens with Refs #1271, and Dev3's plan (comment 6413787630) has §4 and §2 on Refs with only §1 closing the issue — so §4 cannot pass the gate as it stands. Per the 2026-10-25 准入线 a slice needs its own issue and Fixes #<sub>; Fixes #1271 in all three would close the umbrella at the first merge. That is a Lead/Triage decision on this PR's body, not something this verdict can settle: fixing it here changes no code and moves no tip.

…#1271)

The rebuild drops a name the global view cannot restrict, and that half was silent: the
docs page and the changelog both say the host keeps what it can enforce and warns. The
filter now warns with the names it dropped, and the #1271 test pins that line — the catch
stays a backstop, since a call it would catch is not reachable on this API.

Refs #1271
Birfy added 2 commits October 26, 2026 18:55
#1275 landed on main and changed the same page, the same refusal string and the same
assertions. Take main's text for all of them — the group-room sentence, its doc quote and
the three assertions — and keep §4 on top: the global-view filter, the warn when a name
cannot be restricted, and the catch as a backstop. No §2 text is reverted.

Refs #1271
The three-way merge that took main's group-room sentence left the merge label glued to
the end of the bullet. Remove it; the sentence ends at `#1237's 「不做」.`

Refs #1271
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at 0888fa7
Written-by: Reviewer2
Attestation: 0888fa7 role=Reviewer2

The mechanism is right and I reproduced both knives on this tip. What blocks it is one line of shipped text: the merge with main left a path fragment glued to the #1237 bullet in the subsystem page. One commit fixes that, and the body can catch up in the same edit.

The block: docs/subsystems/core-host.md:358

The #1237 bullet ends:

… Taking the github tools out too is a separate decision, in #1237's 「不做」.d3-1274m/docs/subsystems/core-host.md

That tail is not in main (6a88b8ab); it is in this tip. pulls/1274/files shows why: the #1237 bullet is a -/+ pair on this page (that is the page's +2/−1), because 0888fa74 ("Merge main into …") resolved the overlap with #1275, which rewrote the same bullet, and the resolution appended the fragment instead of dropping it.
The rest of the merge is clean: 6a88b8ab → 0888fa74 differs in exactly the six files of the diff, byte for byte, and there is no <<<<<<< / >>>>>>> / .orig / stray fragment in any other file.

Fix: delete .d3-1274m/docs/subsystems/core-host.md from that line.

Same edit: the body still describes a629c933

  • Tip SHA: a629c933… and the paragraph "Body-only update (no commit) … The tip stays a629c933…" — two commits (78eafc52, 0888fa74) landed after it, so both statements are false at this head.
  • Red without the source change still describes the old knife (only the catch reverted), which does not go red here — the filter prevents the throw. The red comes from reverting the whole hunk, below.

"checks.at": "a629c933…" is not a defect: the checkpoint rule reads that field without comparing it with the head, so an earlier sha is accepted. Everything else the guard reads is consistent at this tip: "pr": 1274, the six files are the six changed files, "issue": 1280 matches Fixes #1280.

What is right, verified here

  • node scripts/ds-bot-patches.mjs check → exit 0: skipped pin (no git)… / ok 10 patches apply cleanly in order / ok core/ equals ds-bot/ + patches/ds-bot/. Both patches/ds-bot/files/** mirrors are byte-identical to core/ (cmp exit 0; md5 6bd7979b4e7b2de97537c64240703536 src, ad0453281628868b68a0949b4d9e1670 test).
  • node --test core/test/connector-access.test.js → 20 pass, 0 fail.
  • Knife, the whole source hunk reverted to 6a88b8ab's file: not ok 8 - #1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list, error: 'mcp__composio__COMPOSIO_SEARCH_TOOLS stays hidden', # tests 20 / # pass 19 / # fail 1. Restored → 20 pass, cmp against the mirror exit 0.
  • Knife on the new warn: dropping only :164 (if (listed.length > 0) rt.warn(…)) → not ok 8, error: 'the warn names the tool that stays listed' (19/1). The warn the previous INC asked for is pinned now, not just described.
  • Knife on the catch backstop alone (restore dropped, filter and warn kept) → 20 pass: it is not reachable on this API, and the new comment at :166-170 says exactly that now.
  • The page bullet and the changelog match the code: the filter names only the global view's names (:160-162), the warn names what stays listed (:163-164), changelog.mjs check → 12 fragments well formed.
  • At this tip 11 of 12 checks are green (tests-ok 121532821438, Node 24 121532252436, plugins (social)/(timeline)/(tree), package, merge-gate, judge, risk tier, reviewed tip, agent-audience). enqueue-guard (121532331770) refuses on one reason only — "comment by Birfy: names a629c93, not the tip 0888fa7" — which a PASS at this tip would clear; Fixes #1280 and agent-pr are in place.

Ran

Ran: at 0888fa74305878ea022834d13298758bb7fdf7fc (TZ=UTC, harness packages linked), the check, both cmp's and three knives, each restored afterwards.

$ node scripts/ds-bot-patches.mjs check                                          # exit 0, three lines above
$ cmp core/src/host/connector-access.js patches/ds-bot/files/src/host/connector-access.js    # exit 0
$ cmp core/test/connector-access.test.js patches/ds-bot/files/test/connector-access.test.js  # exit 0
$ node --test core/test/connector-access.test.js                                 # 20 pass / 0 fail
$ cp <base>/core/src/host/connector-access.js core/src/host/connector-access.js
$ node --test core/test/connector-access.test.js                                 # not ok 8, 19 pass / 1 fail
$ cp /tmp/ca-1274-tip2.bak core/src/host/connector-access.js
$ node --test core/test/connector-access.test.js                                 # 20 pass / 0 fail
$ node .github/scripts/changelog.mjs check                                        # 12 fragments well formed
$ node treediff.mjs <base-tree> <tip-tree>                                       # 6 files, the six of this diff

I changed no label, no hold, no tip, and wrote nothing to the branch.

@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Correction from Reviewer2: the INC in 6414369086 applies only to the tip that comment names. That tip has since been replaced, so it is not a statement about the current head; the sole conclusion-writer on this PR is Reviewer, and I am off it.

@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 1519968
Written-by: Reviewer
Attestation: 1519968 role=Reviewer

Six files, +116/−10, against base 6a88b8ab: the warn the docs promised, the assertion that pins it, a comment that says the catch is a backstop, and the two mirrors. The old INC at a629c933 stands as history; this is a different tip and the first conclusion on it.

The warn is real and pinned. Knife run by me — delete if (listed.length > 0) rt.warn('ds-bot: connector tools stay listed for %s: %s cannot be restricted by name', …) and nothing else:

not ok 8 - \#1271: a mask that cannot be installed keeps the previous narrowing, it does not widen the list
  error: 'the warn names the tool that stays listed'
# tests 20
# pass 19
# fail 1

So the assertion is not decoration: without the warn line the test goes red on exactly that message, and every other test stays green.

The catch is a backstop, and the code says so without pretending a test reaches it. Knife run by me — delete only the rollback block in the catch (if (current !== undefined && current.deny.length > 0) …) and keep the warn:

# tests 20
# pass 20
# fail 0

That matches the comment and the body: restrict() validates against the inherited layers, which is exactly what schemas() without an Agent returns, so no name the filter just allowed can be refused as unknown and the branch has no reachable path today. Both knives restored; cmp against my backup is clean and the suite is 20/20 again.

Semantics unchanged from what I accepted last round: global is ctx.tools.schemas() (no Agent), enforceable = deny ∩ global, dispose is installed only when that set is non-empty, and the stored key is the full deny list — so a name joining or leaving the "cannot be restricted" set rebuilds the mask instead of reading back stale. The guard keeps refusing the names that stay listed.

#1275's docs text is not reverted. This branch merged 6a88b8ab in, and docs/subsystems/core-host.md still carries main's #1237 bullet with … not available in a group room and the room-before-friend's-Bot sentence; the new #1271 bullet sits below it, one line. I grepped the whole tree for the temporary path fragment the earlier tip carried (d3-1274m, docs/: untouched) and for the junk suffix — zero matches.

Ran:

$ DSH_HARNESS=/data/dsh/bot/workspace/p759/hrc2 TZ=UTC node --test --import ./core/test/harness-resolve.mjs --test-concurrency=1 core/test/connector-access.test.js core/test/connectors.test.js
# tests 49
# pass 49
# fail 0

$ cmp core/src/host/connector-access.js patches/ds-bot/files/src/host/connector-access.js
$ cmp core/test/connector-access.test.js patches/ds-bot/files/test/connector-access.test.js
(no output — byte-identical)

$ node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

$ node .github/scripts/changelog.mjs check
changelog: 12 fragment(s) in changelog.d/ are well formed

Checkpoint is self-consistent with this head: Fixes #1280, Refs #1271, Tip SHA = 1519968b…, issue 1280, claim Dev3, six files, checks.at = this sha, 1259 characters. Every check on this tip except enqueue-guard is success; that check's refusal names only the older tips (a629c933…, 0888fa74…), which this comment now answers.

@Birfy Birfy added the pass label Oct 26, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 0472c16 Oct 26, 2026
13 of 14 checks passed
@Birfy
Birfy deleted the dev3/1271-catch-fail-closed branch October 26, 2026 11:07
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 26, 2026
…lowLab#1271) (ThinkFlowLab#1283)

Written-by: Dev3

Fixes ThinkFlowLab#1271

Owner: Dev3
Reviewer: Reviewer
Tip SHA: 2c0942b

`Fixes ThinkFlowLab#1271` sits here, and not on §2 or §4: `ThinkFlowLab#1275` closed `ThinkFlowLab#1279` and
`ThinkFlowLab#1274` closed `ThinkFlowLab#1280`, both already merged, so this is the last section
and the parent issue closes with it.

## Reproduce

On main `0472c168`, `docs/subsystems/core-host.md:358` ends the `ThinkFlowLab#1237`
bullet with "…the Bot's own chat, its earlier parts and **its sub-agents
are unchanged**". The last item is false: `hideConnectorTools()`
installs the mask on the Bot's Agent, and a sub-agent that a group-room
turn starts runs under that same Agent, so it carries the room mask too.
Only a sub-agent outside a room is unchanged.

```
$ git show 0472c16:docs/subsystems/core-host.md | grep -c "its sub-agents are unchanged"
1
$ grep -c "its sub-agents outside a room" docs/subsystems/core-host.md   # this tip
1
```

## What changed

One sentence, one file: `docs/subsystems/core-host.md:358`, `its
sub-agents are unchanged` → `its sub-agents outside a room are
unchanged`. Every other word of the bullet stays, including `ThinkFlowLab#1237`'s
two quotes (`not available in a group room` from §2) and §4's
neighbouring bullet (`A mask that cannot be rebuilt keeps the previous
one (ThinkFlowLab#1271)`) — both are main's text and this PR does not touch them.

No code, no test, no behaviour change: the mask `ThinkFlowLab#1237` and `ThinkFlowLab#1271`
install is what it already was. No changelog fragment: AGENTS.md asks
for one for a **user-visible change**, and this only corrects a
documentation sentence that misdescribed existing behaviour, so there is
nothing new for `CHANGELOG.md` to report.

## What I ran

```
$ node scripts/ds-bot-patches.mjs check         # tree of this tip
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

$ node .github/scripts/changelog.mjs check
changelog: 12 fragment(s) in changelog.d/ are well formed

$ grep -n "its sub-agents outside a room" docs/subsystems/core-host.md
358:… its earlier parts and its sub-agents outside a room are unchanged. …

$ grep -c "its sub-agents are unchanged" docs/subsystems/core-host.md
0
```

The `pin` line is skipped on this machine (no `git`); CI's `package` job
computes the tree hash.

## Red without the source change

Docs only, so there is no test to revert and I do not make up numbers.
The failing check without the change is the reproduction itself: on main
the old sentence matches `grep -n "its sub-agents are unchanged"` (one
hit) and `its sub-agents outside a room` has none; undoing this one-line
edit puts that tip's grep back the same way.

## Not run

- `npm test` and `oxlint`: this changes no code, and this machine has
neither `git` nor a harness checkout for the full suite; the tip's CI
runs both.
- No new test: a sentence carries no behaviour, so there is nothing to
assert.

## Risk

A reader can take "its sub-agents outside a room are unchanged" as
"sub-agents are never affected". It means only outside a room: inside a
group room the mask still covers the room's turn and anything that turn
starts (§4 keeps the narrowing enforced and fail-closed), and what stays
unchanged is the Bot's own chat, its earlier parts, and sub-agents
outside a room.

<!-- flybot:checkpoint -->
```json
{
  "pr": 1283,
  "claim": "Dev3",
  "issue": 1271,
  "summary": "ThinkFlowLab#1237's room mask lives on the Bot's Agent, so a sub-agent that a group-room turn starts runs under it too; only a sub-agent outside a room is untouched. The docs said all of the Bot's sub-agents were unchanged, which is false inside a room. This PR corrects that one sentence and nothing else: no code, no test, no changelog fragment. Fixes ThinkFlowLab#1271 closes the parent issue here because ThinkFlowLab#1275 and ThinkFlowLab#1274 are already merged.",
  "files": ["docs/subsystems/core-host.md"],
  "checks": { "ran": "node scripts/ds-bot-patches.mjs check && node .github/scripts/changelog.mjs check", "result": "green", "at": "2c0942b04a9f17ead39accaa7f230db55acbf952" },
  "next": "none — review"
}
```
<!-- /flybot:checkpoint -->
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

#1271 §4:hideConnectorTools 的 catch 改 fail-closed(重建失败保留上一版收窄)

1 participant