feat(brains): sync the capability catalog to the release production serves and bump to 2.9.0 [BRNS-MCP-048] - #27
Conversation
Regenerate the published capability artifacts from the current catalog in ssvlabs/brains. brains-build/SKILL.md gains the three feature rows an earlier sync missed — Board form, Sharing, Recipe publishing — with the routing wording as it now stands; the other four generated skills render byte-identical and do not move. capability-catalog.json carries the refreshed digests and the commit that rendered these bytes. Both client manifests advance 2.8.3 -> 2.9.0 together, so the new content reaches installed plugins and the two stay version-aligned.
Re-renders the published artifacts from the current source commit, so source_commit names the release a user's MCP server is actually running rather than the earlier one this branch first pinned. brains-write/SKILL.md picks up the integration-action contract change: a user who enables direct execution in their brains settings has a short allowlist of low-risk actions run inline from a local CLI client, so an agent must treat any act_on_integration call as potentially executing and never assume a draft_id. Executions are still recorded in /inbox. brains-automation/SKILL.md states the automation-side rule instead — a confirmation-required action drafts unless the automation's owner set write_policy:'auto_confirm_safe'. brains-build/SKILL.md is unchanged from the previous commit; its rows are already at the rendered text for this commit. The manifest advances two artifact digests and source_commit. Both client manifests already carry 2.9.0 from the previous commit, which is the single release that delivers all of this.
The existing branch assertion pinned "`requires_confirmation:true` drafts for out-of-band approval" as an absolute. That is no longer true server-side: a user who enables direct execution has an allowlisted low-risk action run inline from a local CLI client despite requires_confirmation:true. The rendered contract now says "normally drafts", so the pin follows the truth. Its other two clauses are untouched. Adds a pin for the rule that carries the weight once true can execute: the mode is decided server-side per call, so the caller must never assume a draft_id. It is one contiguous substring of the rendered sentence rather than separate includes for each phrase, because two fragments can sit in different sentences or under a contradicting condition and still both be found — that pins vocabulary, not the rule. Mutation-proved both. Reverting to the old absolute wording, deleting the auto_executed row, rewording the false branch, and deleting, paraphrasing, splitting or inverting the per-call rule each turn the suite red with the right message. The artifact digest gate fires first on a lone edit, so each mutation also resealed the manifest — the coordinated edit that clears provenance and is exactly what these semantic pins are for.
…n it Presence checks cannot see what sits next to the sentence they find. Review demonstrated it: leave every pinned sentence intact, insert "the setting is off for almost everyone, so plan on the draft" straight after the never-assume-a- draft_id rule, refresh the digests, and the suite passed while the contract now told an agent the opposite of what it means. Pins the whole rendered contract paragraph as one verbatim region, following the README_INTRO_REGION idiom already in this file. The region runs from the "Discover with" opener to the kind-table header, exclusive, so an insertion anywhere in the prose — including just past the draft_id sentence, where the attack landed — falls inside it. A region ending at that sentence would leave the evasion one character outside and change nothing. The per-rule assertions stay and run first, so a broken rule still reports itself by name; the region is the catch-all for what none of them can see. Ordered the other way it fires first on every in-region edit and the precise messages never surface. This reddens on every upstream reword of the paragraph, harmless ones included. That is intended: the text tells an agent when a call may fire an external write without asking, so a human should read each change before it reaches users. The comment records how to update it correctly, since the tempting fix is to loosen it back to a substring. Mutation-proved, each case also resealing the manifest so the digest gate cannot mask the result: the review's exact insertion, the same contradiction appended at the end of the block, a deleted line, a one-word weakening, a pure reflow, and the previous round's five cases all turn the suite red.
sebastian-ssvlabs
left a comment
There was a problem hiding this comment.
Approving. I looked for anything that breaks an installed plugin or reverses a safety rule for an agent reading these skills, and found nothing critical. Recording what I verified and four medium notes inline.
Verified independently
- All five
artifact_sha256values recomputed from the bytes on this branch — five for five, plussource_commitand both manifests at2.9.0. - Contract suite green on a clean checkout of this branch (
bun run tests/plugin-contract/run.ts). - The insertion attack the last commit describes: I reproduced it against
brains-write— inserted "The setting is off for almost everyone, so plan on the draft" right after thedraft_idsentence and resealed every digest. The region pin fires with the intended message. The defense works as claimed. - Every tool newly routed to from
brains-buildexists on the live MCP surface:create_board_form,share_board,publish_recipe(alsoshare_folder,share_mini_site,create_share_circlenamed in the Sharing row). No repeat of #26.
Nothing here is a breaking change. No tool name is removed or renamed, no schema moves, both manifests advance together, and the one behavioral statement that changed moves published text toward what the server does. Worth stating explicitly because the direction matters: main today tells an agent requires_confirmation:true always drafts, while the server may execute — an agent believing main fires an external write thinking it staged a draft. This PR closes that. Merging sooner is the safer option, not the riskier one.
The four notes below are all medium and none should hold the merge.
stefan-ssv-labs
left a comment
There was a problem hiding this comment.
Reviewed against BRNS-MCP-048's requirement: the plugin skill text may only advertise direct execution ("may execute") once every production MCP replica already runs the SHA that implements it (BRNS-MCP-045 / brains#1270).
Content correctness — independently reproduced, not just trusted. I checked out ssvlabs/brains at the pinned source_commit (e332512644b2f06a6b7068dcc9196eab2d31c05a, confirmed to be the real stage→main merge commit for #1296) in a disposable worktree and ran apps/mcp/scripts/generate-capability-catalog.ts --check --plugin-root <this PR's worktree> --source-commit e332512644b2f06a6b7068dcc9196eab2d31c05a. All 18 generated outputs — including all three republished SKILL.md files and capability-catalog.json — came back fresh, i.e. byte-identical to what's committed here. Worth noting: scripts/generated-artifact-guard.sh's own header explicitly documents that it cannot prove the artifact bytes came from the monorepo generator (private repo, no cross-repo automation) — this reproduction is what closes exactly that gap.
Rollout ordering — the hard requirement. Live production kubectl wasn't available to me in this session, so I confirmed via GitOps instead: gitops-production's main HEAD (ed4a812c, "Updated brains docker images to 656828d28...", committed 2026-08-11T11:43:57Z) targets an image tag that is a confirmed git descendant of the pinned e332512644..., and the brains-production ArgoCD Application (environments/aws/brains.yaml) tracks targetRevision: HEAD with syncPolicy.automated.{prune,selfHeal}: true — no manual sync gate. That's desired-state + auto-sync-policy evidence rather than a live pod read, but it independently corroborates the ordering claim in the PR description.
Test hardening. Read the new WRITE_ACTION_CONTRACT_REGION whole-region pin in tests/plugin-contract/run.ts against the actual rendered text — it correctly captures the current direct-execution behavior (allowlist + per-user opt-in + eligible-client-class gate, not a blanket claim), reuses the existing normalizeRegion helper rather than inventing new comparison logic, and runs after the per-rule includes() checks so a specific rule failure still names itself first. The boundary anchors (Discover with … opener, | \kind` |` table header) are structural rather than editorial, so a reword of the prose between them won't silently move the pin.
Out of scope, correctly. The automation/sandbox-face self-contradiction this PR's description flags (packages/capability-catalog/render.ts / sandbox-catalog.ts in ssvlabs/brains still describing requires_confirmation:false as unconditionally inline, without the allowlist/opt-in nuance the brains-write face already carries) is real — I cross-checked both source locations against this PR's diff. It's already tracked as BRNS-MCP-052 and isn't fixable in this repo (these are rendered, not hand-authored, here), and this PR's own brains-automation/SKILL.md change is an unrelated write_policy:'auto_confirm_safe' addition that doesn't touch or worsen it.
CI is green on this exact head (cross-client plugin contract, generated-artifact + delivery guard, inbox-v2 contract test, tool-error contract test).
No blockers or critical issues. Approving.
|
@stefan-ssv-labs thanks — regenerating from the pinned commit yourself is the check that actually closes the gap the delivery guard documents about itself, and the GitOps evidence is a better ordering proof than my pod read, since it shows the sync policy rather than a moment in time. One correction on the out-of-scope note, so BRNS-MCP-052 doesn't get picked up on the wrong premise. The upstream defect isn't The contradiction is on the That's also why this PR does slightly worsen that file's internal consistency rather than leaving it untouched: it corrects the contract sentence and leaves those two, so the artifact goes from consistent-and-wrong to half-corrected. Both are rendered, so neither is fixable here — the ticket carries the fix plus the automation region pin Sebastian asked for. |
What this does
Syncs the published capability artifacts to
e332512644b2f06a6b7068dcc9196eab2d31c05aand ships it as one release rather than two.Three generated skills move:
brains-build/SKILL.md— three capability rows an earlier sync missed: Board form, Sharing, Recipe publishing. The server has been advertising these tools; the published skill had no route to them.brains-write/SKILL.md— the integration-action contract sentence, see below.brains-automation/SKILL.md— the automation-side rule: a confirmation-required action drafts unless the automation's owner setwrite_policy:'auto_confirm_safe'.capability-catalog.jsonadvances threeartifact_sha256values (brains-write, brains-build, brains-automation), thebrains-featurescatalog_sha256, andsource_commit. Both client manifests go2.8.3→2.9.0. One test file changes; nothing else authored is touched.Reviewer: the part to actually read (BRNS-MCP-048)
brains-write/SKILL.mdnow tells a CLI agent that anact_on_integrationcall may execute inline instead of always drafting. That is the security-relevant change here, and the reason this is pinned to this commit rather than the earlier one.The behaviour is off by default and narrow: a per-user opt-in in brains settings, a three-action allowlist (
gdrive-files/create_file,gcalendar/create_event,monday-items/create_item), each behind an input guard and an audited byte digest, and only for a client signed in from the user's own machine. Everything else drafts. Every execution is still recorded in/inbox. The text says the mode is decided server-side per call and that an agent must never assume adraft_id— literally true: the rate cap degrades a direct execution back to a draft.The server-side change itself shipped upstream in BRNS-MCP-045; this PR only republishes the text that documents it.
This repo's contract tripwire fired on the regenerated artifact, which is worth seeing rather than discovering. It pinned
requires_confirmation:trueas unconditionally drafting, and that is now false server-side. I updated it to the current wording and pinned the whole contract paragraph as a verbatim region — because a substring pin proved bypassable: leaving every pinned sentence intact and inserting "the setting is off for almost everyone, so plan on the draft" kept the suite green while reversing what the contract means. Mutation-proved, that insertion and nine other edits now go red.This PR introduces a transient contradiction in
brains-automation/SKILL.md. Atorigin/mainthat file was internally consistent and wrong: the contract sentence and lines 179 and 251 all said a confirmation-required action waits at/inbox, whenwrite_policy:'auto_confirm_safe'has always executed those inline. This corrects the contract sentence only — the other two are generated from different upstream sources and cannot be fixed here. So the file goes from consistent-and-wrong to half-corrected-and-self-contradicting. Net better, not strictly better: nobody ends up believing something more dangerous than today's published "always waits", but a reader now has to arbitrate two statements. Filed upstream as BRNS-MCP-052.Two smaller upstream gaps the review found, filed with it: the
brains-writetext names the risk but prescribes no "check with the user first" behaviour, and itsCap: 30 auto-executions/install/60sline omits the additional per-user direct-exec cap of 10/min.BRNS-MCP-048's rule is an ordering rule: publish only after every production MCP replica serves the implementing commit. Telling CLI users a call may execute while their server still always drafts is wrong in the direction that matters, and CLI users are the eligible population.
That gate is met. The production
brains-mcpstatefulset is fully rolled — 2/2 replicas ready and updated,currentRevision == updateRevision, both pods on that revision. It now serves656828d2870b27e9c926a0c726b767c222a076e6(a release after this one);e332512is an ancestor of it, and every artifact this PR publishes is byte-identical between the two, so the pinned bytes are what prod runs. That is a cluster check I ran by hand; nothing in CI proves it, so re-check if this sits before merge.Why one digest moves and its neighbour does not
brains-writeandbrains-automationchangeartifact_sha256while theircatalog_sha256holds. That is correct:catalog_sha256digests the authored capability object, and this sentence is authored in the renderer, so the rendered bytes move while the authored catalog does not.brains-featuresmoves both because its change was in the authored feature catalog.Verification
artifact_sha256recomputed from the bytes on disk.source_commit, stamped on the published copy only.brains-boardandbrains-workflowconfirmed unmoved.These artifacts are build output from ssvlabs/brains and sync verbatim — wording changes belong upstream, not here.