返回 DeepSeek-Reasonix
guard.go
1 package runtimepolicy
2
3 import (
4 "encoding/json"
5
6 "reasonix/internal/evidence"
7 )
8
9 // GuardAction is one monotonic preflight verdict.
10 type GuardAction uint8
11
12 const (
13 GuardAbstain GuardAction = iota
14 GuardAllow
15 GuardAsk
16 GuardDeny
17 )
18
19 // GuardDecision is one guard's immutable snapshot.
20 type GuardDecision struct {
21 Action GuardAction
22 Reasons []string
23 Message string
24 }
25
26 // CallContext is the resolved, already-identified tool call.
27 type CallContext struct {
28 ToolName string
29 Args json.RawMessage
30 Profile evidence.EffectProfile
31 PlanReadOnly bool
32 Interactive bool
33 Verification bool
34 TestsForbidden bool
35 WorkspaceRoot string
36 PriorWriteTargets []evidence.TargetKey
37 PriorProductionWrite bool
38 }
39
40 // ResultContext is the frozen post-execute receipt.
41 type ResultContext struct {
42 Seq int
43 Receipt evidence.Receipt
44 Profile evidence.EffectProfile
45 WorkspaceRoot string
46 TestsForbidden bool
47 }
48
49 // Guard is one monotonic pipeline stage.
50 type Guard interface {
51 BeforeTool(CallContext) GuardDecision
52 AfterTool(ResultContext) []evidence.Receipt
53 }
54
55 // MergeDecisions applies Deny > Ask > Allow > Abstain and concatenates
56 // obligations. Later guards cannot revoke a stronger action.
57 func MergeDecisions(decisions ...GuardDecision) GuardDecision {
58 out := GuardDecision{Action: GuardAbstain}
59 for _, d := range decisions {
60 if d.Action > out.Action {
61 out.Action = d.Action
62 if d.Message != "" {
63 out.Message = d.Message
64 }
65 } else if out.Message == "" && d.Message != "" && d.Action == out.Action {
66 out.Message = d.Message
67 }
68 out.Reasons = append(out.Reasons, d.Reasons...)
69 }
70 return out
71 }
72
72 lines GO