package approval
import "encoding/json"
// Verdict is what a set of standing rules says about one tool call, before
// anyone is asked and before anything the session remembers is consulted.
type Verdict int
const (
// NoVerdict means no rule spoke, and the call is decided the ordinary way:
// the mode, the session's memory, and then a prompt.
NoVerdict Verdict = iota
// VerdictAllow lets the call through without a prompt.
VerdictAllow
// VerdictAsk puts the call to a human even when the mode would have let
// it through, and even when the session remembers allowing it.
VerdictAsk
// VerdictDeny refuses the call outright, in every mode, -yolo included.
VerdictDeny
)
// Ruling is a verdict and the rule that produced it, for the message the model
// reads and the prompt a human reads. "Denied" with no reason is a dead end;
// "denied by Bash(rm:*) in ~/.claude/settings.json" is something to fix.
type Ruling struct {
Verdict Verdict
// Rule is the rule as it was written, e.g. Bash(npm run test:*).
Rule string
// Source is where the rule was written, e.g. ~/.claude/settings.json.
Source string
}
// RuleSet is a set of standing rules that act before the session's own
// memory: rules written down in a settings file rather than given as an
// answer to a prompt. Claude Code's permissions.allow, deny and ask are the
// ones that exist so far.
//
// It is an interface so this package stays ignorant of where rules come from
// and of the grammar they are written in. Whoever loads them decides which
// files are honoured; the policy only acts on what they say.
type RuleSet interface {
// Check rules on one call. Deny outranks ask, which outranks allow, so a
// single rule set can never be argued out of a refusal by a broader
// permission elsewhere in it.
Check(tool string, input json.RawMessage) Ruling
// Lines describe the rules for /allowed, grouped by where they came from.
Lines() []string
}
// check is Rules.Check, tolerating a policy with no rules.
func (p *Policy) check(tool string, input json.RawMessage) Ruling {
if p.Rules == nil {
return Ruling{}
}
return p.Rules.Check(tool, input)
}
// RuleLines is what /allowed shows under the session's own memory: the
// standing rules, and the files they came from. Nil when there are none.
func (p *Policy) RuleLines() []string {
if p == nil || p.Rules == nil {
return nil
}
return p.Rules.Lines()
}
// because names the rule behind a ruling, for a message.
func (r Ruling) because() string {
if r.Source == "" {
return r.Rule
}
return r.Rule + " in " + r.Source
}