Phase 04 of hooks Wave 1. Adds infrastructure for shipping canonical hook rows with the binary; Phase 05 plants the first real builtin (pii-redactor). - internal/hooks/builtin: loader (//go:embed yaml+js), Seed() with version reconciliation (newer embed overwrites DB; downgrade warn-only; preserves user's enabled toggle), AllowlistFor() for per-id mutable-field lookup. Stable UUIDv5 namespace 082ab084-a25f-52b4-a4a4-eb8a816bd9a8 keys rows across restarts so Seed is idempotent on every boot. - internal/hooks/store.go: ErrBuiltinReadOnly sentinel + WithSeedBypass ctx marker (seeder bypasses the guard; users cannot). - PG + SQLite hook stores: Update/Delete now reject any patch other than enabled=* on source='builtin' rows. Fail-closed on GetByID errors. - Dispatcher: SetBuiltinAllowlistLookup setter lets the gateway wire the registry-backed strict allowlist; unwired tests keep Phase 03 permissive default. Gateway installs a strip-all lookup first so a Load() failure fails closed instead of opening mutation to the permissive default. - i18n: MsgHookBuiltinReadOnly (en/vi/zh). - yaml.v3 promoted to direct dep. - Tests: loader parse + namespace stability + seed idempotency (3 boots → N rows), version bump, downgrade detection, operator BuiltinDisable escape hatch, PG + SQLite readonly guard round-trip.
2.0 KiB
Builtin Hooks
Canonical, embed-shipped hook rows. Users may toggle enabled but cannot
edit content — the embedded .js is overwritten on every boot.
Adding a new builtin
-
Pick a stable kebab identifier (
pii-redactor,sql-guard). Never rename: the DB primary key isUUIDv5(namespace, id + "/" + event)and renaming would orphan any existing rows. -
Drop
my-feature.jsalongsidebuiltins.yaml. The script sees the goja sandboxed globalseventandresult, plus the usualJSON.*,Math.*. No network, no I/O, no timers. -
Add an entry to
builtins.yaml:- id: my-feature version: 1 events: [pre_tool_use] scope: global timeout_ms: 200 on_timeout: allow priority: 500 mutable_fields: [toolInput.command] source_file: my-feature.js description: "One-line user-facing summary." -
Bump
versionwhenever the JS content changes. On next boot, matching rows get UPDATEd in-place (the user'senabledtoggle is preserved). -
Remove
_placeholder.jsonce this is the first real.jsin the package.
Operator escape hatch
Operators can force-disable any builtin by id via hooks.builtin_disable in
config.json — useful when a builtin misbehaves before the next release:
{
"hooks": {
"builtin_disable": ["pii-redactor"]
}
}
The row stays in the DB (history preserved); only enabled=false is applied.
Version reconciliation rules
| Embed version | DB version | Outcome |
|---|---|---|
| missing row | — | INSERT, enabled=true |
| newer | older | UPDATE content; keep user's enabled toggle |
| equal | equal | no-op |
| older | newer | WARN log; keep DB (no rollback) |
A downgrade happens when someone rolls back the binary but the DB has already been written by a newer version. We never destructively rewrite; operators must resolve manually.