Files
viettranx 8ee540dc7e feat(hooks/builtin): embedded registry + UPSERT seed + store-layer readonly
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.
2026-04-16 14:17:47 +07:00

25 lines
668 B
Go

package builtin
import "github.com/google/uuid"
// AllowlistFor returns the `mutable_fields` list for a registered builtin
// hook row keyed by the per-event UUID (see BuiltinEventID). Returns nil when
// the id is not a registered builtin — caller (dispatcher) treats nil as
// "strip all mutations" for defense-in-depth.
func AllowlistFor(id uuid.UUID) []string {
regMu.RLock()
defer regMu.RUnlock()
if s, ok := eventIDSpec[id]; ok {
return s.MutableFields
}
return nil
}
// IsBuiltin reports whether id belongs to a registered builtin row.
func IsBuiltin(id uuid.UUID) bool {
regMu.RLock()
defer regMu.RUnlock()
_, ok := eventIDSpec[id]
return ok
}