feat(escalate): align config schema with design doc
- Change EscalationConfig to use Routes map with action strings - Rename severity "normal" to "medium" per design doc - Move config from config/ to settings/escalation.json - Add --source flag for escalation source tracking - Add Source field to EscalationFields - Add executeExternalActions() for email/sms/slack with warnings - Add default escalation config creation in gt install - Add comprehensive unit tests for config loading - Update help text with correct severity levels and paths Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -789,70 +789,42 @@ func NewMessagingConfig() *MessagingConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// EscalationConfig represents the escalation system configuration (config/escalation.json).
|
||||
// EscalationConfig represents escalation routing configuration (settings/escalation.json).
|
||||
// This defines severity-based routing for escalations to different channels.
|
||||
type EscalationConfig struct {
|
||||
Type string `json:"type"` // "escalation"
|
||||
Version int `json:"version"` // schema version
|
||||
|
||||
// Enabled controls whether the escalation system is active.
|
||||
Enabled bool `json:"enabled"`
|
||||
// Routes maps severity levels to action lists.
|
||||
// Actions are executed in order for each escalation.
|
||||
// Action formats:
|
||||
// - "bead" → Create escalation bead (always first, implicit)
|
||||
// - "mail:<target>" → Send gt mail to target (e.g., "mail:mayor")
|
||||
// - "email:human" → Send email to contacts.human_email
|
||||
// - "sms:human" → Send SMS to contacts.human_sms
|
||||
// - "slack" → Post to contacts.slack_webhook
|
||||
// - "log" → Write to escalation log file
|
||||
Routes map[string][]string `json:"routes"`
|
||||
|
||||
// DefaultTarget is the address to send escalations when no severity-specific target is set.
|
||||
// Example: "mayor/"
|
||||
DefaultTarget string `json:"default_target,omitempty"`
|
||||
// Contacts contains contact information for external notification actions.
|
||||
Contacts EscalationContacts `json:"contacts"`
|
||||
|
||||
// SeverityRoutes maps severity levels to notification targets.
|
||||
// Keys: "critical", "high", "normal", "low"
|
||||
// Values: EscalationRoute with target addresses and optional external channels
|
||||
SeverityRoutes map[string]EscalationRoute `json:"severity_routes,omitempty"`
|
||||
|
||||
// StaleThreshold is the duration after which an unacknowledged escalation is considered stale.
|
||||
// Format: Go duration string (e.g., "1h", "30m", "24h")
|
||||
// Default: "1h"
|
||||
// StaleThreshold is how long before an unacknowledged escalation
|
||||
// is considered stale and gets re-escalated.
|
||||
// Format: Go duration string (e.g., "4h", "30m", "24h")
|
||||
// Default: "4h"
|
||||
StaleThreshold string `json:"stale_threshold,omitempty"`
|
||||
|
||||
// ExternalChannels configures optional external notification channels (email, SMS, etc.)
|
||||
ExternalChannels *ExternalChannelsConfig `json:"external_channels,omitempty"`
|
||||
// MaxReescalations limits how many times an escalation can be
|
||||
// re-escalated. Default: 2 (low→medium→high, then stops)
|
||||
MaxReescalations int `json:"max_reescalations,omitempty"`
|
||||
}
|
||||
|
||||
// EscalationRoute defines where escalations of a given severity are routed.
|
||||
type EscalationRoute struct {
|
||||
// Targets are the internal addresses to notify (e.g., "mayor/", "gastown/witness")
|
||||
Targets []string `json:"targets"`
|
||||
|
||||
// UseExternal enables external channel notifications for this severity.
|
||||
// If true, checks ExternalChannels config for enabled channels.
|
||||
UseExternal bool `json:"use_external,omitempty"`
|
||||
|
||||
// Channels overrides which external channels to use for this severity.
|
||||
// If empty and UseExternal is true, uses all enabled channels.
|
||||
// Example: ["email"] to only use email for high severity
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
}
|
||||
|
||||
// ExternalChannelsConfig configures external notification channels.
|
||||
type ExternalChannelsConfig struct {
|
||||
// Email configuration for email notifications
|
||||
Email *EmailChannelConfig `json:"email,omitempty"`
|
||||
|
||||
// SMS configuration for SMS notifications (future)
|
||||
SMS *SMSChannelConfig `json:"sms,omitempty"`
|
||||
}
|
||||
|
||||
// EmailChannelConfig configures email notifications.
|
||||
type EmailChannelConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Recipients []string `json:"recipients,omitempty"` // email addresses
|
||||
SMTPServer string `json:"smtp_server,omitempty"`
|
||||
FromAddr string `json:"from_addr,omitempty"`
|
||||
}
|
||||
|
||||
// SMSChannelConfig configures SMS notifications (placeholder for future).
|
||||
type SMSChannelConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Recipients []string `json:"recipients,omitempty"` // phone numbers
|
||||
Provider string `json:"provider,omitempty"` // twilio, etc.
|
||||
// EscalationContacts contains contact information for external notification channels.
|
||||
type EscalationContacts struct {
|
||||
HumanEmail string `json:"human_email,omitempty"` // email address for email:human action
|
||||
HumanSMS string `json:"human_sms,omitempty"` // phone number for sms:human action
|
||||
SlackWebhook string `json:"slack_webhook,omitempty"` // webhook URL for slack action
|
||||
}
|
||||
|
||||
// CurrentEscalationVersion is the current schema version for EscalationConfig.
|
||||
@@ -862,35 +834,53 @@ const CurrentEscalationVersion = 1
|
||||
const (
|
||||
SeverityCritical = "critical" // P0: immediate attention required
|
||||
SeverityHigh = "high" // P1: urgent, needs attention soon
|
||||
SeverityNormal = "normal" // P2: standard escalation (default)
|
||||
SeverityMedium = "medium" // P2: standard escalation (default)
|
||||
SeverityLow = "low" // P3: informational, can wait
|
||||
)
|
||||
|
||||
// ValidSeverities returns the list of valid severity levels in order of priority.
|
||||
func ValidSeverities() []string {
|
||||
return []string{SeverityLow, SeverityMedium, SeverityHigh, SeverityCritical}
|
||||
}
|
||||
|
||||
// IsValidSeverity checks if a severity level is valid.
|
||||
func IsValidSeverity(severity string) bool {
|
||||
switch severity {
|
||||
case SeverityLow, SeverityMedium, SeverityHigh, SeverityCritical:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// NextSeverity returns the next higher severity level for re-escalation.
|
||||
// Returns the same level if already at critical.
|
||||
func NextSeverity(severity string) string {
|
||||
switch severity {
|
||||
case SeverityLow:
|
||||
return SeverityMedium
|
||||
case SeverityMedium:
|
||||
return SeverityHigh
|
||||
case SeverityHigh:
|
||||
return SeverityCritical
|
||||
default:
|
||||
return SeverityCritical
|
||||
}
|
||||
}
|
||||
|
||||
// NewEscalationConfig creates a new EscalationConfig with sensible defaults.
|
||||
func NewEscalationConfig() *EscalationConfig {
|
||||
return &EscalationConfig{
|
||||
Type: "escalation",
|
||||
Version: CurrentEscalationVersion,
|
||||
Enabled: true,
|
||||
DefaultTarget: "mayor/",
|
||||
StaleThreshold: "1h",
|
||||
SeverityRoutes: map[string]EscalationRoute{
|
||||
SeverityCritical: {
|
||||
Targets: []string{"mayor/"},
|
||||
UseExternal: true, // Critical should notify externally by default
|
||||
},
|
||||
SeverityHigh: {
|
||||
Targets: []string{"mayor/"},
|
||||
UseExternal: false,
|
||||
},
|
||||
SeverityNormal: {
|
||||
Targets: []string{"mayor/"},
|
||||
UseExternal: false,
|
||||
},
|
||||
SeverityLow: {
|
||||
Targets: []string{"mayor/"},
|
||||
UseExternal: false,
|
||||
},
|
||||
Type: "escalation",
|
||||
Version: CurrentEscalationVersion,
|
||||
Routes: map[string][]string{
|
||||
SeverityLow: {"bead"},
|
||||
SeverityMedium: {"bead", "mail:mayor"},
|
||||
SeverityHigh: {"bead", "mail:mayor", "email:human"},
|
||||
SeverityCritical: {"bead", "mail:mayor", "email:human", "sms:human"},
|
||||
},
|
||||
Contacts: EscalationContacts{},
|
||||
StaleThreshold: "4h",
|
||||
MaxReescalations: 2,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user