Skip to content

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
jothimani-rajendran merged 3 commits into
mainfrom
claude/plugin-gate
Sep 23, 2026
Merged

jothimani-rajendran merged 3 commits into
mainfrom
claude/plugin-gate

Conversation

@jothimani-rajendran

@jothimani-rajendran jothimani-rajendran commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

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 sync compiled 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 build now packages a policy whose gate declares tool_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, for kind: script, the policy's whole implementations/ under scripts/, so a program that puts its own directory on sys.path still finds what it imports.

  • The hooks file runs the same adapter with --gate on 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:

    Store Write path Turn's end Package posture
    claude PreToolUse on Write|Edit|MultiEdit|NotebookEdit Stop judges the write, re-reads the turn
    codex, devin, copilot none recorded Stop re-reads the turn; says the write itself is not judged
    cursor none recorded no blocking stop hook advisory; no hook installed
  • Each 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 commit stays advisory everywhere. Command-guard packaging is untouched.

Runtime: write_gate looks for the runner beside the gate before the repository layout, requires the .chock/compiled geometry 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 carries script_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 to in_agent_hooks.py to keep in_agent.py in the line budget.

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 the Agent Plugins package and every store package. --check sees a changed or removed file, and every store owns skills/ so a rebuild removes what a policy stopped shipping. This is what lets java-security ship its guided setup page in the one skill that opens it.

Verified against the real thing: the catalog's java-security built with this branch in every format. The Claude package denies the suite's ${} mapper with no selection, stays silent on #{}, honours an allow in the repo's selection, refuses an ask with no terminal, and catches the same file at Stop; the Codex bundle catches it at Stop from the event's working directory.

Definition of done

  • chock check → 0 errors, 0 warnings (pre-existing infos only)
  • chock check --only matrix passes; the matrix is unchanged, no row describes the plugin tier
  • chock sync --repo . --check clean (vendored runtimes regenerated)
  • chock check --only verify clean
  • Registry rescanned; no stale entries
  • pytest -q green: 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's script_base, which vendors a gate reaches, and skill assets in every store
  • pytest acceptance/ -c acceptance/pytest.ini --rootdir=acceptance green: 21 passed
  • Existing artifacts: none affected; a policy without skill/ renders byte-identically (pinned by a test); guard-only packages unchanged
  • Touched manifests: none
  • ruff check . and ruff format --check . clean

Claims

  • No surface is described as enforcing more than it installs. INSTALLED_SURFACES is 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

…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>
Comment thread src/chock/compile/emitters/in_agent.py Fixed
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>
@jothimani-rajendran jothimani-rajendran changed the title feat(plugin): a policy's gate rides in its Claude plugin, and its own skill files ride in its skill feat(plugin): a policy's gate rides in its plugin in every hook-carrying format, and its own skill files ride in its skill Sep 23, 2026
@jothimani-rajendran
jothimani-rajendran marked this pull request as ready for review September 23, 2026 11:00
@jothimani-rajendran
jothimani-rajendran merged commit 6f821cc into main Sep 23, 2026
19 checks passed
@jothimani-rajendran jothimani-rajendran mentioned this pull request Sep 23, 2026
11 tasks
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.

3 participants