mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 04:16:06 +00:00
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.
60 lines
2.0 KiB
Markdown
60 lines
2.0 KiB
Markdown
# 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
|
|
|
|
1. Pick a stable kebab identifier (`pii-redactor`, `sql-guard`). Never rename:
|
|
the DB primary key is `UUIDv5(namespace, id + "/" + event)` and renaming
|
|
would orphan any existing rows.
|
|
2. Drop `my-feature.js` alongside `builtins.yaml`. The script sees the
|
|
[goja](https://github.com/dop251/goja) sandboxed globals `event` and
|
|
`result`, plus the usual `JSON.*`, `Math.*`. No network, no I/O, no timers.
|
|
3. Add an entry to `builtins.yaml`:
|
|
|
|
```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."
|
|
```
|
|
|
|
4. Bump `version` whenever the JS content changes. On next boot, matching
|
|
rows get UPDATEd in-place (the user's `enabled` toggle is preserved).
|
|
5. Remove `_placeholder.js` once this is the first real `.js` in 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:
|
|
|
|
```json
|
|
{
|
|
"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.
|