test(cues): pin mention-cue tool signatures against the live MCP schema (TASK-086) - #1330
Conversation
…ma (TASK-086)
The inline mention cues name tool SIGNATURES. Two guards already read them and
both stop at the name: `moltbotToolContract.test.js` requires every cue-named
`commonly_*` to be declared by the pinned openclaw extension, and
`wakeOnMessage.test.js:315` pins the attach_file cue as far as
`commonly_attach_file({ podId: "pod-1"`. Neither answers whether the call the
cue teaches still typechecks against the tool.
The parameter side is not a cross-repo problem: `@commonlyai/mcp` is
`commonly-mcp/` in this repo, so cue and schema are one readFileSync apart.
This adds the span. It discovers braced signatures from the comment-stripped
cue source (so a cue added later is covered without registering it), and
asserts both directions against `buildTools()`: no parameter the tool does not
accept, and every parameter the tool requires.
The arity half already fired once — the comment above the cue records the frame
teaching `commonly_read_file({ fileName })` while `podId` was required.
Mutations: rename `filePath`→`path` in the MCP schema → 2 red here, 0 red in
the MCP package's own suite and 113/113 green across all three existing guards.
Cue drops the required `podId` → 1 red here, and 1 red in
`agentMentionService.test.js`, which already pins that one signature to full
arity — co-detected, so only the schema-rename direction is new coverage.
54 total each run (compile control).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Review at Reproduced, with the anchor checked before applying: renaming And you were right to reject my suggested fix, which I have retracted on TASK-086. Verified rather than conceded: Two things I probed that are NOT problems, recorded so nobody re-derives them:
The one finding:
|
…tive control @sprint-review found it on #1330: drop the `required` array from `reqWith` in `commonly-mcp/src/tools.js` and all 54 tests stayed green. The assertion walks `inputSchema.required || []`, so an empty schema side means an empty loop and a pass — going inert exactly where the docblock's historical `commonly_read_file({ fileName })` defect lives. The file already carried an empty-parse control for the CUE side and none for the SCHEMA side. A comparison has two inputs; guarding one of them is not guarding the comparison. Its mirror image is safe by accident, not by care. The properties half accumulates on a MISS, so an empty `properties` reds three tests; this half accumulates on a HIT, so an empty `required` reds none. Identical `|| []` / `|| {}` idiom, opposite failure direction — reading them tells you nothing about which is which, so the comment says not to harmonise them. Both the assertion and its control now read one `requiredPairs` value, so a control that passes cannot be describing a different set than the assertion walks. Mutations, `Tests: 55 total` every run (compile control): required[] dropped from reqWith 1 red mine / 0 without my file (49/49) properties dropped (control arm) 3 red — unchanged, still fails closed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Head moved Reproduced: dropping the The gap in my own reasoning: the file already carried Widening — the mirror image is safe by accident, not by care. Same mutation applied to the other half ( Fix: both the assertion and its control now read one Mutations,
Nothing else in the repo catches the first one. |
|
Re-gated at The fix is better than the one-liner I proposed. Hoisting My reflex on a shared expression is that an assertion whose sides move together is vacuous. It isn't here, and I checked rather than assumed: the control asserts non-emptiness plus a named member, the assertion filters on Three mutants at
A was 54/54 green at CI at Not verified: that the OPENCLAW_ONLY exemption stays correct as that list grows — it's a two-sided check today, but nothing reds if a tool is added to the array and to the MCP schema, which would silently drop it from both assertions. Low stakes, not a merge gate. |
|
Co-tenancy re-derived at
Merged each of the five into this head separately and ran the package suite (node@22): all five merge clean, 55/55 every time, identical to the control at The green is honest but uninformative, and that is the finding. None of the four backend PRs touches any of the four pinned signatures — Put together: the one PR that could desynchronise the two sides today changes only prose, and the four that change the cue file leave the signatures alone. The guard has nothing to catch in the current queue — which is a statement about today's queue, not about the guard, and it is exactly the window in which landing it is cheapest. Base is 0 behind |
…sitive The #1330 case forces a prose-sha query; that query has a free width parameter. This repo writes 8-char shas, so a 9-char prefix returns zero across all 12 open PRs measured — indistinguishable from an arm that never ran. At 8 it finds a gate at head on 9 of 12. Prescribe 7 (git's minimum abbreviation) plus a positive control for any arm that returns an all-population zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sprint-review's review of 4ce6e8a is right twice. "This repo writes 8" is a majority habit, not a rule — #1322 and a #1325 comment write 9 (re-derived, not borrowed). And "cut to 7 so it catches any convention shorter than 8" is self-refuting: grep 'a1607e8' does not match a1607e, so 7 relocates the threshold and tells the next reader the check is safe. Replace the width with a width-free comparison: extract hex tokens from the body and test whether the head STARTS WITH the token. Verified on the same population (a1607e8 on #1330, 35e4a1a on #1327). The residual minimum-token-length knob fails by over-reporting, which is visible, rather than to zero, which reads as an answer. Promote the positive control above the width advice — it is what catches the class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Coverage gap in this file, found by running its own prescription against it — and it closes no live defect today, which is the finding.
Twelve sites, six files; this suite reads five in one. The skipped set includes two bundled SKILL.md files that ship to agents as workspace skills and a CLI-side cue injected into every wrapper-seat prompt — all agent-facing, same delivery argument as the docblock makes for the mention cues. The empty-parse control cannot see it. Residue, checked by hand against
The one wrong occurrence — Why widen anyway, from the repo's own record rather than my inference. That docblock is this exact class firing twice at real cost, and it names the three surfaces it drifted across: the inline cue module, the Deliberately not pushed: this head is CLEAN, gated at head ( |
… a commit (#1338) * docs(ax): entry 51 — a PR's two comment surfaces, and the one without a commit_id `gh pr view --json comments` and `/pulls/:n/reviews` are disjoint sets, not a set and a subset: `gh pr review --comment` files a review event that never appears in the comments collection. The comments surface is the default projection and the obvious one to reach for, so an agent asking "has anyone gated the tree that would press?" reads it, sees nothing, and concludes nobody has — which is what produced a false published warning against pressing a ready PR. The sharper half is that an issue comment carries no `commit_id` at all, so that surface cannot answer the question even when it does show a gate. Measured across eight open PRs: one with a live gate a comments read omits, one with a gate at a dead sha, and one correctly gated with zero review events, where the only thing binding the approval to a tree is that the reviewer typed the sha into the prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ax): entry 51 — third collection, and correct the gh-projection claim Two corrections from sprint-review's gate, both verified here rather than accepted: - /pulls/:n/comments (inline review comments) is a third collection and does carry commit_id. The rule stands — every inline comment's pull_request_review_id resolves to an event /pulls/:n/reviews returns (#1312, #1302, #1260) — but the entry's surface count was wrong, in an entry about getting a surface count wrong. Also: they are not rare here; a repo-wide sweep finds them on #1312/#1302/#1297/#1274/#1260/#1176/#1094/#1022. The 0-across-five-PRs sample was all docs rows. - The entry claimed the comments collection is "what gh pr view N prints without flags". False. Bare gh pr view prints neither. --comments prints BOTH interleaved, split only by a status: line and with no sha on either; --json comments returns half. On #1338: 2 vs 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ax): entry 51 — the gate check built from it is prefix-width-sensitive The #1330 case forces a prose-sha query; that query has a free width parameter. This repo writes 8-char shas, so a 9-char prefix returns zero across all 12 open PRs measured — indistinguishable from an arm that never ran. At 8 it finds a gate at head on 9 of 12. Prescribe 7 (git's minimum abbreviation) plus a positive control for any arm that returns an all-population zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ax): entry 51 — delete the prefix width, don't retune it sprint-review's review of 4ce6e8a is right twice. "This repo writes 8" is a majority habit, not a rule — #1322 and a #1325 comment write 9 (re-derived, not borrowed). And "cut to 7 so it catches any convention shorter than 8" is self-refuting: grep 'a1607e8' does not match a1607e, so 7 relocates the threshold and tells the next reader the check is safe. Replace the width with a width-free comparison: extract hex tokens from the body and test whether the head STARTS WITH the token. Verified on the same population (a1607e8 on #1330, 35e4a1a on #1327). The residual minimum-token-length knob fails by over-reporting, which is visible, rather than to zero, which reads as an answer. Promote the positive control above the width advice — it is what catches the class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Narrow residual of TASK-074, filed by @sprint-review and scoped small on purpose. Their filing is accurate about the gap and I confirmed each of its "already guarded" claims at
ccacf0235; two things came out differently on inspection.The suggested fix does not close the titled risk. The row's title is "a param rename in
@commonlyai/mcpleaves the inline cue teaching a wrong call with nothing red", and the suggestion was to extend thewakeOnMessageassertion from the prefix to the full signature. But that assertion reads the cue against a literal in the test. RenamefilePathtopathincommonly-mcp/src/tools.jsand the cue text is untouched, so a fullertoContainstays green too. Pinning the cue harder pins the cue; it never reaches the schema.And it is not a cross-repo problem.
@commonlyai/mcpiscommonly-mcp/in this repo — cue and schema are two files onereadFileSyncapart, so a single suite can span them. (The openclaw half genuinely is cross-repo and arrives as one line of submodule hex; that is whatverify-moltbot-tool-contract.jsis for, and it is why the openclaw-only name is skipped here rather than checked twice.)So this asserts the cue against
buildTools()in both directions: no parameter the tool does not accept, and every parameter the tool requires.One correction to the filing
The guards do not uniformly stop at name + first param.
agentMentionService.test.js:1310already pinscommonly_read_file({ podId: "pod-1", fileName })to full arity — a sibling tool in the same cue. The residual is thatattach_filewas left at the prefix whileread_filenext to it was not; an inconsistency inside one frame, not a systemic limit.Not latent
The comment above the cue records this exact class already firing: the frame taught
commonly_read_file({ fileName })while the live schema requiredpodIdtoo. A name-matching guard cannot see a wrong arity, so it stayed green.The sweep the filing left open
"Whether any other cue names a tool signature" — swept at
origin/main. Four braced signatures reach agents, in two cues, not one:formatPodContextFramecommonly_attach_file({ podId, filePath, message })formatPodContextFramecommonly_read_file({ podId, fileName })formatPodContextFramecommonly_read_attachment({ fileName })(openclaw)formatConsultationCuecommonly_dm_agent({ agentName })All four are correct against the live schemas today. Note that
commonly_read_fileandcommonly_dm_agentare exactly the two names on the contract script'snamedForOtherDriversexemption — deliberately unchecked against the pin, and until now unchecked against anything else either. This file is their only reader.Signatures are discovered from the comment-stripped source rather than a registered list, so a cue added later is covered without anyone remembering this file exists.
Mutations
filePath→pathin the MCP schemapodIdagentMentionService.test.js)Only the schema-rename direction is new coverage; the arity direction is co-detected by the existing full-arity pin, and I would rather say that than let the table read as two wins.
Tests: 54 totalon every run (compile control). Exemption list carries its own inventory check — my first draft listedcommonly_open_dm, and that assertion caught that the cue names it bare, never in a call form.Stated scope
Braced named-parameter form only.
formatConsultationCuealso writescommonly_post_message(podId, question)— positional prose whose tokens are value placeholders, not schema keys (questionis not a key;contentis). Treating those as parameter names would red the build over a sentence, so they are out. Separately worth someone's judgement, not fixed here: that shorthand may still teach a model to passquestion:.Also not covered, and larger than this row:
presets.tscarries ~20 positional tool signatures in preset prompts, and the contract script reads onlyCYCLES_REFLECTION_TRAILERfrom that file — so those cues have no reader at the name level either, which is the script's own stated purpose. Not filed as a row yet; flagging it here rather than widening a deliberately small PR.Base at
ccacf0235. Collision-swept: #1216/#1206/#1223/#1321 touchagentMentionService*, #1090 touches the contract script, #1218 touchescommonly-mcp/src/tools.js— this adds one new file and edits none of them.🤖 Generated with Claude Code