Skip to content

fix(agents): the guard's scope was answering a question nobody re-asked - #847

Merged
lilyshen0722 merged 1 commit into
mainfrom
fix/widen-guard-to-mention-frame
Aug 5, 2026
Merged

fix(agents): the guard's scope was answering a question nobody re-asked#847
lilyshen0722 merged 1 commit into
mainfrom
fix/widen-guard-to-mention-frame

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Widens scripts/verify-moltbot-tool-contract.js from one source of agent-facing text to two — the trigger #843 named, now that #842 has landed on the lines in question.

The finding this produced

The guard shipped reading only the cycles reflection trailer. That scope was deliberate and documented. What it also did was fix the answer to "how many tools is the fleet told to call that its runtime doesn't have?" at one — a number that reached CLAUDE.md, three PR bodies and the audit doc, and that nobody re-derived once the check went green.

Re-running the widened contract against 00821479, the pin that was live until today's deploy:

required:  log_cycle  attach_file  read_attachment  post_message  get_messages  open_dm
MISSING:   log_cycle             read_attachment                              open_dm

Three, not one. commonly_open_dm was named to openclaw seats by the consultation cue — on every mention, to every agent — while the pinned extension did not declare it. Same defect as commonly_log_cycle, on a surface roughly 100× wider than the heartbeat, uncounted for the same 88 days. Its absence was independently known (CLAUDE.md records it); nothing connected it to the cue demanding it.

Why the naive widening would be wrong

The cues name commonly_read_file and commonly_dm_agent (MCP) beside commonly_read_attachment and commonly_open_dm (openclaw), because they ship to every seat unconditionally. That pairing is the fix from #842 and audit entry #13. Feeding the cue through the contract unchanged would demand an MCP tool of an openclaw pin and red the build over a line that is deliberately correct — a guard that reds on the fix is worse than no guard.

So a source declares namedForOtherDrivers, and the exemption is itself checked: a name listed there that no longer appears in the cue is a hard error. An exemption that outlives its justification is a hole that would go on excusing the next real violation.

Failure directions, verified by mutation against the live files

Not asserted in a test — mutated, run, restored, restore confirmed:

mutation exit
a cue starts naming an unclassified tool 1 FAIL, attributed to the cue
an exempted name is removed from the cue 2 CANNOT VERIFY, names the stale entry
a new cue is defined but not registered 2 CANNOT VERIFY, names the cue

The third is the coverage-gap guard. Without it a new cue ships tool names nobody checks while the guard still prints OK — this PR's own defect, reproduced inside the fix for it. Throws from the source layer now exit 2 instead of crashing out as 1: could-not-read is not contract-violated.

Also

  • The PASS line now names the sources it read and states what it did not cover. Same reasoning: a green check is read as "nothing is wrong", and the narrower the scope the more confidently that is over-read.
  • Drops live since 11878b43c from the consultation cue's comment. That commit is on the lineage .gitmodules declared, not the one the gitlink tracked — the sentence was false for 88 days and is true today only because the pin moved. No ref replaces it: this check reads that cue on every CI run, so the claim has a reader instead of a citation.
  • Audit entry Increase backend test coverage #15.

Test

guard suite            35 pass, 0.7s
agentMentionService    87 pass (both suites together)
live run               exit 0 — 6 required, all declared at 70bd82b8
lint                   clean

Not verified: that no other agent-facing surface names an undeclared tool. Two sources are covered; registry presets beyond the trailer, MCP tool descriptions, and the agent-runtime context payload are not — and nothing yet reports that they aren't.

🤖 Generated with Claude Code

The tool contract shipped in #843 read one source of agent-facing text — the
cycles reflection trailer. That scope was deliberate and stated: #818/#842 were
open on the inline mention cues, and a guard straddling an open PR is a merge
conflict rather than a safeguard. #842 has landed, so this is the widening it
named.

What the narrow scope also did was fix the answer to "how many tools is the
fleet told to call that its runtime does not have?" at one. That number reached
CLAUDE.md, the PR body and the audit doc, and nobody re-derived it once the
check was green. Re-running the widened contract against 00821479 — the pin
that was live until today's deploy:

  required:  log_cycle attach_file read_attachment post_message
             get_messages open_dm
  MISSING:   log_cycle             read_attachment          open_dm

Three, not one. commonly_open_dm was named to openclaw seats by the
consultation cue on EVERY mention while the pinned extension did not declare
it — the same defect as log_cycle on a surface ~100x wider, uncounted for the
same 88 days. Its absence was separately known; nothing connected it to the cue
demanding it.

The naive widening is wrong, which is why this took care. The cues name
commonly_read_file and commonly_dm_agent (MCP) beside commonly_read_attachment
and commonly_open_dm (openclaw) because they ship to every seat unconditionally
— that pairing IS the fix from #842 and audit entry #13. Requiring an MCP name
of an openclaw pin would red the build over a deliberately correct line. So a
source declares namedForOtherDrivers, and the exemption is self-checking: a
name exempted but no longer present in the cue is a hard error, because an
exemption outliving its justification is a hole that would excuse the next real
violation.

Failure directions verified by mutation against the live files, not asserted:

  unclassified new name in a cue          exit 1  (FAIL, names the cue)
  exemption no longer in the cue          exit 2  (CANNOT VERIFY)
  a cue defined but not registered        exit 2  (CANNOT VERIFY)

The third is the coverage-gap guard: a new cue would otherwise ship tool names
nobody checks while the guard still printed OK, reproducing this commit's own
defect inside the fix for it. Throws from the source layer now exit 2 rather
than crashing out as 1 — could-not-read is not contract-violated.

The PASS line now names the sources it read and says what it did not cover, for
the same reason: a green check is read as "nothing is wrong", and the narrower
the scope the more confidently that is over-read.

Also drops "live since 11878b43c" from the consultation cue's comment. That
commit is on the lineage .gitmodules DECLARED, not the one the gitlink tracked,
so the sentence was false for 88 days and is true now only because the pin
moved. No ref replaces it: this check reads that cue on every CI run, so the
claim has a reader instead of a citation.

Guard: 35 tests, 0.7s. agentMentionService: 87 pass with the suite above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722
lilyshen0722 merged commit eeeba55 into main Aug 5, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the fix/widen-guard-to-mention-frame branch August 5, 2026 20:16
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