|
1
|
package registry |
|
2
|
|
|
3
|
import ( |
|
4
|
"fmt" |
|
5
|
"strings" |
|
6
|
) |
|
7
|
|
|
8
|
// EngagementConfig is the rules-of-engagement configuration for a registered agent. |
|
9
|
// Passed to Register() at registration time; signed into the payload returned to the agent. |
|
10
|
type EngagementConfig struct { |
|
11
|
// Channels is the list of IRC channels the agent should join. |
|
12
|
Channels []string `json:"channels,omitempty"` |
|
13
|
|
|
14
|
// OpsChannels is a subset of Channels where the agent is granted +o (operator). |
|
15
|
// Only meaningful for orchestrator-type agents. |
|
16
|
OpsChannels []string `json:"ops_channels,omitempty"` |
|
17
|
|
|
18
|
// Permissions is the list of allowed action types (e.g. "task.create"). |
|
19
|
// Empty means no explicit restrictions. |
|
20
|
Permissions []string `json:"permissions,omitempty"` |
|
21
|
|
|
22
|
// RateLimit controls message throughput for this agent. |
|
23
|
RateLimit RateLimitConfig `json:"rate_limit,omitempty"` |
|
24
|
|
|
25
|
// Rules defines engagement behaviour rules for this agent. |
|
26
|
Rules EngagementRules `json:"engagement,omitempty"` |
|
27
|
} |
|
28
|
|
|
29
|
// RateLimitConfig controls message throughput. |
|
30
|
type RateLimitConfig struct { |
|
31
|
// MessagesPerSecond is the sustained send rate allowed. 0 means no limit. |
|
32
|
MessagesPerSecond float64 `json:"messages_per_second,omitempty"` |
|
33
|
|
|
34
|
// Burst is the maximum burst above MessagesPerSecond. 0 means no burst. |
|
35
|
Burst int `json:"burst,omitempty"` |
|
36
|
} |
|
37
|
|
|
38
|
// EngagementRules defines what message types and peers the agent should engage with. |
|
39
|
type EngagementRules struct { |
|
40
|
// RespondToTypes restricts which message types trigger handler callbacks. |
|
41
|
// Empty means respond to all types. |
|
42
|
RespondToTypes []string `json:"respond_to_types,omitempty"` |
|
43
|
|
|
44
|
// IgnoreNicks is a list of IRC nicks whose messages are always ignored. |
|
45
|
IgnoreNicks []string `json:"ignore_nicks,omitempty"` |
|
46
|
} |
|
47
|
|
|
48
|
// Validate checks the EngagementConfig for obvious errors. |
|
49
|
// Returns a descriptive error for the first problem found. |
|
50
|
func (c EngagementConfig) Validate() error { |
|
51
|
for _, ch := range c.Channels { |
|
52
|
if !strings.HasPrefix(ch, "#") { |
|
53
|
return fmt.Errorf("engagement: channel %q must start with #", ch) |
|
54
|
} |
|
55
|
if strings.ContainsAny(ch, " \t\r\n,") { |
|
56
|
return fmt.Errorf("engagement: channel %q contains invalid characters", ch) |
|
57
|
} |
|
58
|
if len(ch) < 2 { |
|
59
|
return fmt.Errorf("engagement: channel %q is too short", ch) |
|
60
|
} |
|
61
|
} |
|
62
|
|
|
63
|
// OpsChannels must be a subset of Channels. |
|
64
|
joinSet := make(map[string]struct{}, len(c.Channels)) |
|
65
|
for _, ch := range c.Channels { |
|
66
|
joinSet[ch] = struct{}{} |
|
67
|
} |
|
68
|
for _, ch := range c.OpsChannels { |
|
69
|
if _, ok := joinSet[ch]; !ok { |
|
70
|
return fmt.Errorf("engagement: ops_channel %q is not in channels list", ch) |
|
71
|
} |
|
72
|
} |
|
73
|
|
|
74
|
if c.RateLimit.MessagesPerSecond < 0 { |
|
75
|
return fmt.Errorf("engagement: rate_limit.messages_per_second must be >= 0") |
|
76
|
} |
|
77
|
if c.RateLimit.Burst < 0 { |
|
78
|
return fmt.Errorf("engagement: rate_limit.burst must be >= 0") |
|
79
|
} |
|
80
|
|
|
81
|
for _, t := range c.Rules.RespondToTypes { |
|
82
|
if t == "" { |
|
83
|
return fmt.Errorf("engagement: respond_to_types contains empty string") |
|
84
|
} |
|
85
|
} |
|
86
|
|
|
87
|
return nil |
|
88
|
} |
|
89
|
|