feat(plugin): a policy's gate rides in its plugin in every hook-carrying format, and its own skill files ride in its skill - #158
Merged
Conversation
…y's own skill files ride in its skill A plugin installs at the agent, not in a repository, so it carried a command guard or nothing: a gate lived only where chock sync compiled it, and a policy like java-security was advisory in the marketplace while enforcing in every repo install. chock plugin build --format claude now packages a policy whose gate declares tool_use: the compiled scripts/gate.json, the runner beside it as scripts/gate.py, and for kind: script the whole implementations/ under scripts/, so a program that imports from beside itself still does. hooks/hooks.json runs the same adapter with --gate on the vendor's recorded write tools at PreToolUse and, with no matcher, at Stop. The package states the posture and the skill claims its hooks; a gate that declares only commit stays advisory, since a hook that could only refuse is not installed. The bundled runtime looks for the runner beside the gate before the repository layout, requires the compiled layout before deriving a root from it, and otherwise takes the repository from the event's working directory -- where a policy's own config, such as a selection file, is read from. A packaged script gate says script_base: gate, which the runner reads as beside me. Runtime goldens and this repo's vendored runtimes regenerate. A policy may also carry a skill/ folder: body.md joins its rendered SKILL.md after the constraint block and every other file rides in the skill's directory, in both the Agent Plugins and the Claude package. --check treats a changed or removed file as drift, and the Claude store owns skills/ so a rebuild removes what a policy stopped shipping. The hooks-file builders move from in_agent.py to in_agent_hooks.py (re-exported) to keep the emitter inside the line budget. Signed-off-by: Claude <noreply@anthropic.com>
CodeQL flagged the re-export block in in_agent.py: three of the names it carried were imported for other modules' benefit and used by nothing in the file. The plugin emitters and the wire-facts test now import from in_agent_hooks directly, and in_agent keeps only what it calls. Signed-off-by: Claude <noreply@anthropic.com>
…g what the vendor records The Claude emitter packaged a policy's gate; the four other stores still packaged command guards only. The packaging moves to plugin/gate_package and every store uses it, wired to the surfaces agentseam records for the vendor and no other: Claude Code gates its recorded write tools at PreToolUse and the turn's end at Stop; Codex, Devin and Copilot record no write-tool vocabulary but block at the turn's end, so their packages carry the gate at Stop alone and say the write itself is not judged; Cursor records neither, so its package stays advisory rather than installing a hook that could only refuse. Each store's posture keeps its own caveat (Codex's trust review, Devin's best-effort hooks, Copilot's namespace and plugin-root condition). A policy's skill/ files ride in every store's package, and every store owns skills/ so a rebuild removes what a policy stopped shipping. A test pins which vendors a gate reaches as agentseam's answer, so a change upstream surfaces rather than silently widening a package. Signed-off-by: Claude <noreply@anthropic.com>
11 tasks
jothimani-rajendran
marked this pull request as ready for review
September 23, 2026 11:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A plugin installs at the agent, not in a repository, so until now it carried a command guard or nothing: a gate lived only where
chock synccompiled it.java-security(chock-catalog) enforces in every repo install and was advisory in every plugin marketplace, where the person who wants it as a plugin actually meets it.chock plugin buildnow packages a policy whose gate declarestool_use, in every hook-carrying format, through one shared module (plugin/gate_package.py):scripts/gate.json(the compiled gate),scripts/gate.py(the stdlib runner, verbatim) and, forkind: script, the policy's wholeimplementations/underscripts/, so a program that puts its own directory onsys.pathstill finds what it imports.The hooks file runs the same adapter with
--gateon every surface agentseam records for the vendor, and on no other. A test pins the answer so an upstream change surfaces instead of silently widening a package:PreToolUseonWrite|Edit|MultiEdit|NotebookEditStopStopEach package states its posture with the vendor's own caveat kept (Codex's trust review, Devin's best-effort hooks, Copilot's namespace and plugin-root condition); the skill claims its hooks. A gate that declares only
commitstays advisory everywhere. Command-guard packaging is untouched.Runtime:
write_gatelooks for the runner beside the gate before the repository layout, requires the.chock/compiledgeometry before deriving a root from it (it used to accept any path four deep), and otherwise takes the repository from the event's working directory, where a policy's own config such as a selection file is read. A packaged script gate carriesscript_base: gate, which the runner reads as "beside me" instead of "under the repository root". Runtime goldens and this repo's vendored runtimes regenerate; the hooks-file builders move toin_agent_hooks.pyto keepin_agent.pyin the line budget.A policy may also carry a
skill/folder:body.mdjoins its renderedSKILL.mdafter the constraint block and every other file rides in the skill's directory, in the Agent Plugins package and every store package.--checksees a changed or removed file, and every store ownsskills/so a rebuild removes what a policy stopped shipping. This is what letsjava-securityship its guided setup page in the one skill that opens it.Verified against the real thing: the catalog's
java-securitybuilt with this branch in every format. The Claude package denies the suite's${}mapper with no selection, stays silent on#{}, honours anallowin the repo's selection, refuses anaskwith no terminal, and catches the same file atStop; the Codex bundle catches it atStopfrom the event's working directory.Definition of done
chock check→ 0 errors, 0 warnings (pre-existing infos only)chock check --only matrixpasses; the matrix is unchanged, no row describes the plugin tierchock sync --repo . --checkclean (vendored runtimes regenerated)chock check --only verifycleanpytest -qgreen: 1459 passed, 2 skipped; new tests cover packaging in every store, drift, the bundled runtime on the packaged layout (write, stop, missing runner) on the Claude and Codex bundles, the runner'sscript_base, which vendors a gate reaches, and skill assets in every storepytest acceptance/ -c acceptance/pytest.ini --rootdir=acceptancegreen: 21 passedskill/renders byte-identically (pinned by a test); guard-only packages unchangedruff check .andruff format --check .cleanClaims
INSTALLED_SURFACESis repo-shaped and unchanged; each package describes its own reach in its description, a stop-only package says the write is not judged, and Cursor installs no hook for a gate.🤖 Generated with Claude Code