Skip to content

Commit 1de74e1

Browse files
grypezclaude
andcommitted
docs(caprock): add package code-organization reference
Descriptive overview of how the plugin is laid out: the hook subprocess, the src library (with bash.ts as the AST entry point), the in-kernel permission-tracker vat, and the communication boundary between them. Complements the forward-looking pipeline-rewrite-plan. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e4d2b52 commit 1de74e1

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Caprock plugin: code organization
2+
3+
Reference for how `@ocap/caprock` is laid out and how its parts talk to each
4+
other. Descriptive of the current package; for forward-looking design see
5+
[`caprock-pipeline-rewrite-plan.md`](./caprock-pipeline-rewrite-plan.md).
6+
7+
## What it is
8+
9+
A Claude Code plugin (`@ocap/caprock`, private) that intercepts every Claude
10+
tool invocation via hooks, routes the structured invocation through a
11+
sheaf-backed permission vat running inside ocap-kernel, and returns
12+
allow/ask/deny. Per-session state and an append-only event log live in
13+
`~/.caprock/`.
14+
15+
## Top-level layout
16+
17+
```
18+
packages/caprock/
19+
├── .claude-plugin/plugin.json ← Claude Code plugin manifest (name, version)
20+
├── hooks/hooks.json ← one entry per hook event, all → bin/hook.mjs
21+
├── package.json ← name=@ocap/caprock; ships dist/ + vat/ + hooks/ + skills/
22+
├── bin/ ← CLI entrypoints (compiled to dist/bin/*.mjs)
23+
├── src/ ← library code (compiled to dist/index.mjs)
24+
├── vat/ ← in-kernel vat code (bundled to .bundle, committed)
25+
├── scripts/ ← thin shell wrappers around bin/ for user-facing CLIs
26+
├── skills/{audit,setup,status}/SKILL.md ← slash-command surfaces
27+
└── docs/ ← this file, and the rewrite plan
28+
```
29+
30+
## The three layers
31+
32+
### 1. Hook layer — `bin/`
33+
34+
Single entrypoint `hook.ts` wired to every Claude hook event by
35+
`hooks/hooks.json`. Reads JSON payload from stdin, dispatches to one of:
36+
`onSessionStart`, `onPreToolUse`, `onPostToolUse`, `onPermissionRequest`, etc.
37+
38+
Per-event responsibilities:
39+
40+
- **SessionStart** — boot session: `ensureDaemon()`, `launchPermissionVat()`,
41+
`createKernelSession()`, persist `SessionState`.
42+
- **PreToolUse**`buildClauses(tool_name, tool_input)` → for Bash,
43+
`decompose()` from `src/bash.ts`; for other tools, wrap as one clause. Route
44+
each clause via `vatRoute()`. If allow → continue; else → `authorizeRequest()`
45+
(kernel surfaces a TUI prompt).
46+
- **PostToolUse / PermissionDenied / FileChanged** — record events, refresh
47+
provisioned list.
48+
- **SessionEnd** — write summary, tear down kernel session.
49+
50+
Other `bin/` tools:
51+
52+
- `setup.ts` — installer health checks (tree-sitter native binding, kernel
53+
daemon).
54+
- `status.ts` — pretty-print current session state.
55+
- `audit.ts` — replay a transcript against current rules; the only file with
56+
regex (used to match Claude Code permission _globs_, not bash syntax).
57+
- `harden-shim.ts` — minimal shim because `@endo` lockdown is incompatible with
58+
the native tree-sitter binding.
59+
60+
### 2. RPC / state layer — `src/`
61+
62+
Library code consumed by `bin/` (and exported via `src/index.ts` for
63+
embedding).
64+
65+
- **`bash.ts`** — the AST core. `decompose(source) → DecomposeResult` parses
66+
with tree-sitter-bash, dispatches via `SAFETY_FRAGMENT` (a table of
67+
recognized AST node kinds → handler), returns clauses or refuses with a
68+
named `DropReason`. Plus security checks (`hasCurlPipeShell`,
69+
`hasEvalDynamic`). This is the single entry point for bash understanding in
70+
the whole package.
71+
- **`rpc.ts`** — minimal JSON-RPC client over the kernel's UNIX socket (no
72+
`@endo` deps — keeps the hook small and lockdown-free). Exports
73+
`sendCommand`, `createKernelSession`, `authorizeRequest`,
74+
`recordProvisioned`. The kernel side is the ocap-kernel daemon.
75+
- **`session.ts`** — pure persistence: load/save `SessionState`, append events
76+
to `<session-id>.jsonl`, read settings allow/deny lists from
77+
`.claude/settings.json`.
78+
- **`transcript.ts`** — parse Claude Code transcript JSONL (used by audit).
79+
- **`types.ts`**`SessionState`, `CaprockEvent` / `CaprockEventKind`, hook
80+
payload types (`PreToolUsePayload`, etc.).
81+
- **`paths/`** — three small modules resolving filesystem locations: `user.ts`
82+
(HOME), `plugin.ts` (plugin install root), `ocap-kernel.ts` (caprock data
83+
dir, kernel binary path).
84+
- **`index.ts`** — three re-exports (`types`, `session`, `rpc`). Tiny.
85+
86+
### 3. In-vat layer — `vat/`
87+
88+
`permission-tracker.ts` (plus the committed `.bundle` artifact). This is the
89+
part that _runs inside the kernel_, not in the hook process.
90+
91+
- Built into a vat bundle by `yarn build` (the build script also bundles).
92+
- Per Claude session, the hook launches one of these vats. It maintains a
93+
sheaf of `Provider<Meta>` capabilities — one per `Provision` (a permission
94+
grant).
95+
- Methods: `route(tool, invocations)` (the dispatch), `addSection(provision)`
96+
(grant), `findMatch`, `listProvisions`, `size`.
97+
- Uses `@metamask/sheaves` (`sheafify`, `leastAuthority`, `makeHandler`) — the
98+
actual sheaf machinery lives there, this is just the per-permission
99+
encoding.
100+
- Uses `@metamask/kernel-utils/session` for `computeAuthority`, `matchPattern`,
101+
`matchProvision`, the `Provision` type — the shared parser/authority model.
102+
This package only provides the _vat-side_ of it.
103+
104+
## Communication boundaries
105+
106+
```
107+
Claude Code ocap-kernel daemon
108+
───────────── ──────────────────
109+
fires hook
110+
111+
bin/hook.ts (subprocess)
112+
uses src/bash.ts ───────┐ each session →
113+
uses src/session.ts │ ┌──────────────────────┐
114+
uses src/rpc.ts ───── UNIX socket → permission-tracker vat
115+
writes ~/.caprock/ │ │ (vat/permission-tracker.ts)
116+
reads ~/.claude/ │ │ sheaf of Provisions
117+
│ └──────────────────────┘
118+
returns allow/ask/deny ◄──┘
119+
```
120+
121+
The hook process is short-lived, runs once per event, holds no shared state.
122+
All durable state is either on disk (`~/.caprock/`) or in the kernel daemon's
123+
vat. The hook never imports anything from `vat/` directly — the only contact
124+
is via JSON-RPC over the socket.
125+
126+
## The bash layer's primacy
127+
128+
Bash understanding is funneled through `src/bash.ts` from a single entry point
129+
(`decompose()`), gated by a positive `SAFETY_FRAGMENT` so unknown AST shapes
130+
refuse with `unsupported_construct` rather than fall through to a permissive
131+
walk. Extending the recognized set is a one-line `SAFETY_FRAGMENT` entry plus
132+
tests — a deliberate decision per AST node kind, not an accident of "it
133+
happened to parse." This is the foundation the planned rewriter (per-stage
134+
`caprock` wrapping, per
135+
[`caprock-pipeline-rewrite-plan.md`](./caprock-pipeline-rewrite-plan.md)) will
136+
sit on top of.
137+
138+
## Skills + scripts (user-facing CLIs)
139+
140+
`skills/{audit,setup,status}/SKILL.md` are Claude slash-command definitions.
141+
Each delegates to a thin shell wrapper in `scripts/` (e.g., `scripts/status.sh`
142+
`node dist/bin/status.mjs`). The split lets the underlying TS get
143+
type-checked and bundled while the surface remains shell-callable.
144+
145+
## What lives _outside_ this package
146+
147+
Worth knowing where the boundaries are:
148+
149+
- The sheaf algebra (`sheafify`, `leastAuthority`, sections, lifts) —
150+
`@metamask/sheaves`.
151+
- The `Provision` data model, `computeAuthority`, pattern matching —
152+
`@metamask/kernel-utils/session`.
153+
- The kernel daemon, vat lifecycle, TUI prompting — the broader ocap-kernel.
154+
- Tree-sitter parser — `tree-sitter` + `tree-sitter-bash` (native bindings;
155+
needs the `harden-shim` workaround).
156+
157+
Caprock itself is mostly _glue and policy_: parse the bash, plumb the RPC,
158+
persist the session, and host one specific vat that encodes "this permission
159+
grant means this routing rule."

0 commit comments

Comments
 (0)