Vectrum
Sign In
73 lines · 2.5 KB · Go
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
}