Why: Squad members never saw the direct-run stop, reminder, and before-tool gates, and there was no shared place to decide deny, ask, or allow. Evidence: GOWORK=off go test ./runtime/hooks/ ./runtime/policy/ ./runtime/ ./adapters/native/ passed, including an A/B run where hook adapters match the old callbacks. Impact: An empty bus leaves stop reason, reminder count, and deny count unchanged. Callers opt in by setting GateOptions. Refs: feature/permission-hooks Co-authored-by: Cursor <cursoragent@cursor.com>
41 lines
930 B
Go
41 lines
930 B
Go
package hooks
|
|
|
|
import "testing"
|
|
|
|
func TestDenyBeatsAllow(t *testing.T) {
|
|
b := New()
|
|
b.On(func(Call) Outcome { return Outcome{Decision: Allow} })
|
|
b.On(func(c Call) Outcome {
|
|
if c.Event == PreToolUse {
|
|
return Outcome{Decision: Deny, Reason: "no"}
|
|
}
|
|
return Outcome{}
|
|
})
|
|
out := b.Emit(Call{Event: PreToolUse, Tool: "write_file"})
|
|
if out.Decision != Deny || out.Reason != "no" {
|
|
t.Fatalf("%+v", out)
|
|
}
|
|
}
|
|
|
|
func TestAdaptBefore(t *testing.T) {
|
|
h := AdaptBefore(func(name, args string) error {
|
|
if name == "write_file" {
|
|
return errString("blocked")
|
|
}
|
|
return nil
|
|
})
|
|
if h(Call{Event: PreToolUse, Tool: "read_file"}).Decision != Allow {
|
|
t.Fatal("read")
|
|
}
|
|
if h(Call{Event: PreToolUse, Tool: "write_file"}).Decision != Deny {
|
|
t.Fatal("write")
|
|
}
|
|
if h(Call{Event: Stop}).Decision != "" {
|
|
t.Fatal("stop should be ignored")
|
|
}
|
|
}
|
|
|
|
type errString string
|
|
|
|
func (e errString) Error() string { return string(e) }
|