Documentation
¶
Overview ¶
Package approval implements the human-in-the-loop gate (CLAUDE.md headline feature). A side-effecting tool call that policy gates pauses until a human approves or denies it. Policy evaluation is deterministic — no LLM decides whether something needs approval.
Index ¶
- type Decision
- type Gate
- func (g *Gate) Create(ctx context.Context, req Request) (storage.ApprovalRecord, bool, error)
- func (g *Gate) Get(ctx context.Context, id string) (storage.ApprovalRecord, error)
- func (g *Gate) Pending(ctx context.Context) ([]storage.ApprovalRecord, error)
- func (g *Gate) Request(ctx context.Context, req Request) (Decision, string, error)
- func (g *Gate) RequestUnder(ctx context.Context, req Request, policy Policy) (Decision, string, error)
- func (g *Gate) Required(tool, sideEffect string) bool
- func (g *Gate) Resolve(ctx context.Context, id string, approved bool, reason, by string) error
- type Notifier
- type Policy
- type Request
- type Rule
- type SlackAction
- type SlackNotifier
- func (s *SlackNotifier) Notify(_ context.Context, a storage.ApprovalRecord)
- func (s *SlackNotifier) ParseInteraction(payload []byte) (SlackAction, bool)
- func (s *SlackNotifier) UpdateResolved(ctx context.Context, act SlackAction, a storage.ApprovalRecord)
- func (s *SlackNotifier) VerifySignature(timestamp, signature string, body []byte) bool
- type WebhookNotifier
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate is the human-in-the-loop approval queue. It persists pending approvals, notifies push channels, and blocks the calling (tool) goroutine until a human resolves the request via the API/CLI/web — or the run's context is cancelled.
func (*Gate) Create ¶
Create evaluates policy and, if approval is required, persists a pending approval and notifies push channels, returning the record (required=true). Unlike Request, it does NOT block — the caller (e.g. the SDK over HTTP) polls the approval's status instead. Returns required=false when policy allows the call outright.
func (*Gate) Request ¶
Request gates a side-effecting call. If approval is not required it returns an approved Decision immediately. Otherwise it persists a pending approval, notifies push channels, and BLOCKS until the request is resolved or ctx is done (run cancel / time budget). This is how a side-effecting call "pauses".
func (*Gate) RequestUnder ¶ added in v0.7.0
func (g *Gate) RequestUnder(ctx context.Context, req Request, policy Policy) (Decision, string, error)
RequestUnder is Request, but evaluates the supplied policy instead of the gate's default. It lets a run enforce its own policy bundle's approval rules per-run (a referenced policyRef) rather than only the daemon-global policy.
type Notifier ¶
type Notifier interface {
Notify(ctx context.Context, a storage.ApprovalRecord)
}
Notifier pushes a newly-pending approval to a channel (e.g. a webhook). The CLI and web page are pull channels and do not implement this.
func CombineNotifiers ¶ added in v0.6.0
CombineNotifiers returns a single Notifier over the given channels, dropping nil ones. Returns nil when none remain (the Gate treats nil as "no push channel"), or the sole notifier unwrapped when only one is configured.
type Policy ¶
type Policy struct {
// RequireFor lists match rules; a call needs approval if it matches ANY rule.
RequireFor []Rule
// DefaultSafe, when true, requires approval for ANY call with a non-empty side
// effect even if no rule matched — fail closed on side effects.
DefaultSafe bool
}
Policy is the deterministic rule set deciding which calls need approval. It mirrors the api/v1 ApprovalPolicy schema.
type Request ¶
type Request struct {
RunID string
StepIndex int32
Tool string
SideEffect string
Arguments map[string]any
}
Request describes a side-effecting tool call seeking approval.
type Rule ¶
Rule matches a tool call by exact tool name or by a side-effect glob (e.g. "*write*"). A rule with both set matches if EITHER matches.
type SlackAction ¶ added in v0.6.0
type SlackAction struct {
ApprovalID string
Approve bool
By string
Channel string // for updating the source message
MessageTS string
}
SlackAction is a parsed Approve/Deny click: which approval, the decision, the human who clicked, and the message to update.
type SlackNotifier ¶ added in v0.6.0
type SlackNotifier struct {
// contains filtered or unexported fields
}
SlackNotifier is a push channel that posts a pending approval to a Slack channel with Approve/Deny buttons, and verifies the signed interaction Slack sends back when a human clicks one. Outbound uses a bot token (chat.postMessage); inbound is authenticated by the app's signing secret (not the daemon's API token, which Slack can't send). The bot token and signing secret are secrets — read from the environment, never logged. See SECURITY.md.
func NewSlackNotifier ¶ added in v0.6.0
func NewSlackNotifier(botToken, channel, signingSecret string, log *slog.Logger) *SlackNotifier
NewSlackNotifier returns a notifier, or nil if the bot token or channel is unset (no Slack push channel). A missing signing secret still allows outbound posts but makes the inbound interaction endpoint fail closed (it can't verify Slack).
func (*SlackNotifier) Notify ¶ added in v0.6.0
func (s *SlackNotifier) Notify(_ context.Context, a storage.ApprovalRecord)
Notify posts the approval to Slack asynchronously (best-effort; a flaky Slack must never wedge a governed run).
func (*SlackNotifier) ParseInteraction ¶ added in v0.6.0
func (s *SlackNotifier) ParseInteraction(payload []byte) (SlackAction, bool)
ParseInteraction decodes a Slack block_actions payload into a SlackAction, or (_, false) if it isn't a recognized Approve/Deny click.
func (*SlackNotifier) UpdateResolved ¶ added in v0.6.0
func (s *SlackNotifier) UpdateResolved(ctx context.Context, act SlackAction, a storage.ApprovalRecord)
UpdateResolved rewrites the source Slack message to show the decision, replacing the buttons so it can't be actioned twice. Best-effort.
func (*SlackNotifier) VerifySignature ¶ added in v0.6.0
func (s *SlackNotifier) VerifySignature(timestamp, signature string, body []byte) bool
VerifySignature checks a Slack request signature (v0 scheme: HMAC-SHA256 of "v0:{timestamp}:{body}" keyed by the signing secret), rejecting a stale timestamp to defeat replay. Returns false (fail closed) if no signing secret is configured. Constant-time compare.
type WebhookNotifier ¶
type WebhookNotifier struct {
// contains filtered or unexported fields
}
WebhookNotifier POSTs a JSON notification to a user-configured URL when an approval becomes pending. This is user-configured outbound network (like the OTLP endpoint) — RiskKernel calls it only because the user set the URL. It does NOT include any provider keys or secrets. See SECURITY.md.
func NewWebhookNotifier ¶
func NewWebhookNotifier(url string, log *slog.Logger) *WebhookNotifier
NewWebhookNotifier returns a notifier, or nil if url is empty (no push channel).
func (*WebhookNotifier) Notify ¶
func (w *WebhookNotifier) Notify(_ context.Context, a storage.ApprovalRecord)
Notify fires the webhook asynchronously (best-effort; failures are logged, never fatal — a flaky webhook must not wedge a governed run).