Documentation
¶
Index ¶
- Variables
- func AcknowledgeAlertWithTimeline(alertID uuid.UUID, userName string, via models.AcknowledgmentVia, ...) error
- func FetchTelegramChatID(ctx context.Context, botToken string) (string, string, error)
- func SetAIService(svc IncidentService, ai AIService)
- func SetCommanderDeps(svc IncidentService, userRepo repository.UserRepository, ...)
- func SetTeamsService(svc IncidentService, teams *TeamsService)
- func SetTelegramService(svc IncidentService, tg *TelegramService)
- func SupportedCountryCodes() []string
- func TestTeamsCredentials(ctx context.Context, appID, appPassword, tenantID, teamID string) (string, error)
- func TestTelegramConnection(ctx context.Context, botToken, chatID string) (string, error)
- type AIService
- type AlertLookupFunc
- type AlertService
- type AuthService
- type BotAccount
- type BotActivity
- type BotConversation
- type BuiltInTemplateError
- type Channel
- type ChatService
- type CreateActionItemParams
- type CreateCommentParams
- type CreateIncidentParams
- type CreatePostMortemTemplateParams
- type CreateTimelineEntryParams
- type EscalationEngine
- type EscalationNotifier
- type GroupingAction
- type GroupingDecision
- type GroupingEngine
- type HolidayService
- type IncidentService
- type LocalAuthService
- type Message
- type PostMortemService
- type ProcessingResult
- type RoutingDecision
- type RoutingEngine
- type ScheduleEvaluator
- type SlackEventHandler
- type SlackMessageBuilder
- func (b *SlackMessageBuilder) BuildAlertLinkedMessage(alert *models.Alert, incident *models.Incident) Message
- func (b *SlackMessageBuilder) BuildEscalationDMMessage(alert *models.Alert, tierIndex int) Message
- func (b *SlackMessageBuilder) BuildIncidentCreatedMessage(incident *models.Incident, alerts []models.Alert) Message
- func (b *SlackMessageBuilder) BuildIncidentUpdatedMessage(incident *models.Incident) Message
- func (b *SlackMessageBuilder) BuildShiftChannelNotification(scheduleName, layerName, outgoingUser, incomingUser string, shiftEnd time.Time) Message
- func (b *SlackMessageBuilder) BuildShiftHandoffIncomingMessage(scheduleName, layerName string, shiftEnd time.Time) Message
- func (b *SlackMessageBuilder) BuildShiftHandoffOutgoingMessage(scheduleName, layerName, incomingUser string) Message
- func (b *SlackMessageBuilder) BuildStatusUpdateMessage(incident *models.Incident, previousStatus models.IncidentStatus, ...) Message
- type SlackValidator
- type TeamMember
- type TeamsEventHandler
- type TeamsService
- func (s *TeamsService) ArchiveChannel(channelID string) error
- func (s *TeamsService) CreateChannel(name, description string) (*Channel, error)
- func (s *TeamsService) GetThreadMessages(channelID, threadTS string) ([]string, error)
- func (s *TeamsService) InviteUsers(channelID string, userIDs []string) error
- func (s *TeamsService) PostMessage(channelID string, msg Message) (string, error)
- func (s *TeamsService) PostToChannel(teamsChannelID string, msg Message) (conversationID, activityID string, err error)
- func (s *TeamsService) PostToConversation(conversationID string, msg Message) (string, error)
- func (s *TeamsService) SendDirectMessage(userID string, msg Message) error
- func (s *TeamsService) SendOnCallDM(aadObjectID string, incident *models.Incident, appURL string) error
- func (s *TeamsService) UpdateConversationMessage(conversationID, activityID string, msg Message) error
- func (s *TeamsService) UpdateMessage(channelID, messageID string, msg Message) error
- type TelegramService
- type TimelineSegment
- type UpdateActionItemParams
- type UpdateIncidentParams
- type UpdatePostMortemParams
- type UpdatePostMortemTemplateParams
Constants ¶
This section is empty.
Variables ¶
var ErrSlackNotConfigured = errors.New("slack not configured")
ErrSlackNotConfigured is returned by LazySlackService when no valid Slack config exists in the database. Callers that treat "not configured" as a no-op should check for this error with errors.Is.
Functions ¶
func AcknowledgeAlertWithTimeline ¶
func AcknowledgeAlertWithTimeline( alertID uuid.UUID, userName string, via models.AcknowledgmentVia, engine EscalationEngine, incidentRepo repository.IncidentRepository, timelineRepo repository.TimelineRepository, ) error
AcknowledgeAlertWithTimeline acknowledges an alert via the escalation engine and, if the alert is linked to an incident, appends a timeline entry recording who acknowledged it, when, and via which channel.
This is the canonical acknowledgment call used by the REST handler and Slack button callback — both paths converge here so timeline entries are always written.
func FetchTelegramChatID ¶
FetchTelegramChatID calls getUpdates to find the most recent group chat ID the bot has seen. Returns chat_id and chat title of the most recent group/supergroup.
func SetAIService ¶
func SetAIService(svc IncidentService, ai AIService)
SetAIService wires the optional AI service into the incident service. Called by routes.go after construction to avoid changing the constructor signature.
func SetCommanderDeps ¶
func SetCommanderDeps(svc IncidentService, userRepo repository.UserRepository, scheduleRepo repository.ScheduleRepository, evaluator ScheduleEvaluator)
SetCommanderDeps wires the optional user/schedule dependencies for commander auto-assignment. Called by routes.go after construction. Safe to skip in tests that don't test this feature.
func SetTeamsService ¶
func SetTeamsService(svc IncidentService, teams *TeamsService)
SetTeamsService wires the optional Teams service into the incident service (v0.8+). Called by routes.go after construction when TEAMS_APP_ID is configured.
func SetTelegramService ¶
func SetTelegramService(svc IncidentService, tg *TelegramService)
SetTelegramService wires the optional Telegram service into the incident service. Called by routes.go after construction when Telegram is configured in DB.
func SupportedCountryCodes ¶
func SupportedCountryCodes() []string
SupportedCountryCodes returns the list of country codes supported for holiday sync.
func TestTeamsCredentials ¶
func TestTeamsCredentials(ctx context.Context, appID, appPassword, tenantID, teamID string) (string, error)
TestTeamsCredentials validates Teams credentials by fetching team info from the Graph API. Returns the team display name on success. Intended for use by the setup wizard test endpoint.
Types ¶
type AIService ¶
type AIService interface {
// IsEnabled returns true if the AI service is properly configured.
IsEnabled() bool
// Reload reinitialises the LLM clients with the given provider and credentials.
// Passing an empty apiKey (for cloud providers) or empty ollamaBaseURL disables AI features.
Reload(provider, apiKey, model, ollamaBaseURL string)
// GenerateSummary generates a concise incident summary using all available context.
// slackMessages should be the plain-text messages from the incident Slack thread.
// Pass an empty slice when Slack is not configured or the channel has no messages.
GenerateSummary(ctx context.Context, incident *models.Incident, timeline []models.TimelineEntry, alerts []models.Alert, slackMessages []string) (string, error)
// GenerateHandoffDigest generates a structured shift handoff document.
// Suitable for posting to Slack or displaying in the UI at shift change.
GenerateHandoffDigest(ctx context.Context, incident *models.Incident, timeline []models.TimelineEntry, alerts []models.Alert) (string, error)
// GeneratePostMortem generates a full post-mortem document in Markdown.
// sections is the ordered list of section names from the chosen template.
// Uses a higher token budget than summary generation.
GeneratePostMortem(ctx context.Context, incident *models.Incident, timeline []models.TimelineEntry, alerts []models.Alert, sections []string) (string, error)
// EnhancePostMortem improves an existing post-mortem draft for clarity,
// structure, and completeness while preserving all factual details.
EnhancePostMortem(ctx context.Context, content string) (string, error)
// EnhanceIncidentDraft converts a rough user brief into a polished incident
// title and summary. Returns structured title + summary strings.
EnhanceIncidentDraft(ctx context.Context, brief string) (title, summary string, err error)
// AnswerQuestion answers a natural-language question asked in a Slack channel.
// postMortems maps "INC-NNN" to the full post-mortem markdown for that incident.
// Returns a Slack-formatted reply (mrkdwn).
AnswerQuestion(ctx context.Context, question string, current *models.Incident, similar []models.Incident, postMortems map[string]string) (string, error)
}
AIService provides AI-powered features for incidents. When no provider is configured, the service is disabled; callers must check IsEnabled().
func NewAIService ¶
func NewAIService(provider, apiKey, model string, maxTokens, pmMaxTokens int, ollamaBaseURL string) AIService
NewAIService creates a reloadable AIService backed by the given provider. If the provider cannot be initialised (e.g. empty key, missing Ollama URL) the service starts disabled; call Reload after credentials are saved in Settings.
type AlertLookupFunc ¶
AlertLookupFunc retrieves an alert by ID; injected for testing.
type AlertService ¶
type AlertService interface {
// ProcessAlertmanagerPayload processes Prometheus Alertmanager webhooks (legacy method for v0.1 compatibility)
ProcessAlertmanagerPayload(payload *webhooks.AlertmanagerPayload) (*ProcessingResult, error)
// ProcessNormalizedAlerts processes alerts from any source after normalization (v0.3+)
ProcessNormalizedAlerts(alerts []webhooks.NormalizedAlert) (*ProcessingResult, error)
// SetGroupingEngine sets the grouping engine for alert deduplication and grouping (v0.3+)
SetGroupingEngine(engine GroupingEngine)
// SetRoutingEngine sets the routing engine for alert routing decisions (v0.3+)
SetRoutingEngine(engine RoutingEngine)
// SetEscalationEngine sets the escalation engine for alert escalation (v0.5+)
SetEscalationEngine(engine EscalationEngine)
// SetEscalationRepos provides repos for the severity-rule and global-fallback
// steps in the escalation policy resolution chain.
SetEscalationRepos(escalationRepo repository.EscalationPolicyRepository, systemSettingsRepo repository.SystemSettingsRepository)
}
AlertService defines the interface for alert processing operations
func NewAlertService ¶
func NewAlertService(alertRepo repository.AlertRepository, incidentSvc IncidentService) AlertService
NewAlertService creates a new alert service
type AuthService ¶
type AuthService interface {
UpsertFromSAML(ctx context.Context, subject, issuer, email, name string) error
}
AuthService handles user provisioning from SAML assertions.
func NewAuthService ¶
func NewAuthService(userRepo repository.UserRepository, ossUserLimit int) AuthService
type BotAccount ¶
type BotActivity ¶
type BotActivity struct {
Type string `json:"type"`
ID string `json:"id"`
Text string `json:"text"`
ChannelID string `json:"channelId"` // "msteams"
Conversation BotConversation `json:"conversation"`
From BotAccount `json:"from"`
Recipient BotAccount `json:"recipient"`
ServiceURL string `json:"serviceUrl"`
ChannelData json.RawMessage `json:"channelData"`
}
BotActivity is a Bot Framework Activity JSON object (simplified subset we use).
type BotConversation ¶
type BuiltInTemplateError ¶
type BuiltInTemplateError struct{ Name string }
BuiltInTemplateError is returned when trying to modify or delete a built-in template.
func (*BuiltInTemplateError) Error ¶
func (e *BuiltInTemplateError) Error() string
type Channel ¶
type Channel struct {
// ID is the platform-specific channel identifier
// Examples: "C01234567" (Slack), "19:xxx@thread.tacv2" (Teams)
ID string
// Name is the human-readable channel name
// Examples: "inc-042-api-gateway-errors"
Name string
// URL is the direct link to the channel in the platform's web/app UI
URL string
}
Channel represents a chat channel with platform-specific details.
type ChatService ¶
type ChatService interface {
// CreateChannel creates a new channel with the given name and description.
// The channel should be public by default.
//
// Parameters:
// - name: The channel name (will be sanitized by implementation)
// - description: Optional channel description/topic
//
// Returns:
// - Channel with ID, Name, and URL populated
// - Error if creation fails (auth, network, name collision, etc.)
//
// Thread-safe: Yes
CreateChannel(name, description string) (*Channel, error)
// PostMessage posts a message to the specified channel.
//
// Parameters:
// - channelID: The platform-specific channel identifier
// - message: The message to post (text + optional blocks)
//
// Returns:
// - Message timestamp/ID for future updates
// - Error if posting fails (invalid channel, permissions, rate limits, network, etc.)
//
// Thread-safe: Yes
PostMessage(channelID string, message Message) (string, error)
// UpdateMessage updates an existing message in a channel.
//
// Parameters:
// - channelID: The platform-specific channel identifier
// - messageTS: The message timestamp/ID returned from PostMessage
// - message: The updated message content
//
// Returns:
// - Error if update fails (message not found, invalid channel, permissions, rate limits, network, etc.)
//
// Thread-safe: Yes
UpdateMessage(channelID, messageTS string, message Message) error
// ArchiveChannel archives the specified channel.
// Archived channels are read-only and hidden from active channel lists.
//
// Parameters:
// - channelID: The platform-specific channel identifier
//
// Returns:
// - Error if archiving fails (channel not found, permissions, already archived, network, etc.)
//
// Thread-safe: Yes
ArchiveChannel(channelID string) error
// InviteUsers invites users to a channel.
// Used for auto-inviting specific users to incident channels.
//
// Parameters:
// - channelID: Platform-specific channel identifier
// - userIDs: List of platform-specific user identifiers
//
// Returns:
// - Error if invitation fails (permissions, invalid IDs, rate limits, network)
//
// Implementation notes:
// - Empty userIDs list should return nil (no-op)
// - Handle rate limits with exponential backoff
// - Partial failures should return descriptive error
//
// Thread-safe: Yes
InviteUsers(channelID string, userIDs []string) error
// SendDirectMessage sends a direct message to a user by their display name or email.
// The implementation is responsible for resolving the username to a platform user ID
// and opening a DM conversation.
//
// Parameters:
// - username: Display name or email of the recipient (as stored in ScheduleParticipant.UserName)
// - message: The message to send
//
// Returns:
// - Error if lookup, DM open, or send fails
// - Graceful no-op (returns nil) if Slack is not configured
//
// Thread-safe: Yes
SendDirectMessage(username string, message Message) error
// GetThreadMessages fetches the plain-text messages from a channel thread.
// Used to provide Slack conversation context to the AI summarizer.
//
// Parameters:
// - channelID: The platform-specific channel identifier
// - threadTS: The parent message timestamp identifying the thread
//
// Returns:
// - Slice of plain-text message strings (author + text, one per message)
// - Empty slice (not error) if the thread is empty or has no text messages
// - Error only for auth/network failures
//
// Thread-safe: Yes
GetThreadMessages(channelID, threadTS string) ([]string, error)
}
ChatService defines the interface for chat platform operations. This abstraction enables support for multiple chat platforms (Slack, Teams) while keeping business logic platform-agnostic.
Implementations should: - Handle authentication internally - Implement exponential backoff for rate limits - Return descriptive errors for debugging - Be safe for concurrent use
func NewLazySlackService ¶
func NewLazySlackService(repo repository.SlackConfigRepository) ChatService
NewLazySlackService returns a ChatService backed by the given config repo. It is always non-nil; individual calls return ErrSlackNotConfigured when Slack has not been set up yet.
func NewMultiChatService ¶
func NewMultiChatService(providers ...ChatService) ChatService
NewMultiChatService creates a ChatService that fans DMs out to all providers.
func NewSlackService ¶
func NewSlackService(token string) (ChatService, error)
NewSlackService creates a new Slack implementation of ChatService. It validates the token on initialization by calling auth.test.
Parameters:
- token: Slack bot token (xoxb-...)
Returns:
- ChatService implementation
- Error if token is invalid or auth fails
type CreateActionItemParams ¶
type CreateCommentParams ¶
type CreateIncidentParams ¶
type CreateIncidentParams struct {
Title string
Severity models.IncidentSeverity
Description string
CreatedBy string // "user", "system", "api"
AIEnabled bool // Controls whether AI agents process this incident. Defaults to true.
}
CreateIncidentParams holds parameters for creating a manual incident
type CreateTimelineEntryParams ¶
type CreateTimelineEntryParams struct {
IncidentID uuid.UUID
Type string
Content models.JSONB
ActorType string
ActorID string
}
CreateTimelineEntryParams holds parameters for creating a timeline entry
type EscalationEngine ¶
type EscalationEngine interface {
TriggerEscalation(alert *models.Alert) error
TriggerIncidentEscalation(incidentID uuid.UUID, policyID uuid.UUID) error
EvaluateEscalations() error
AcknowledgeAlert(alertID uuid.UUID, by string, via models.AcknowledgmentVia) error
MarkAlertCompleted(alertID uuid.UUID) error
}
EscalationEngine drives the escalation lifecycle for alerts.
Responsibilities:
- TriggerEscalation: called by the alert processing pipeline when a new alert is linked to a policy.
- EvaluateEscalations: called by the background worker every 30 s to send notifications and advance timed-out tiers.
- AcknowledgeAlert: called by the acknowledgment API (Slack, REST, CLI).
- MarkAlertCompleted: called when an alert resolves before being acknowledged.
func NewEscalationEngine ¶
func NewEscalationEngine( repo repository.EscalationPolicyRepository, evaluator ScheduleEvaluator, notifier EscalationNotifier, ) EscalationEngine
NewEscalationEngine creates a new escalation engine. notifier may be nil when chat is not configured; notifications are skipped.
type EscalationNotifier ¶
type EscalationNotifier interface {
// SendEscalationDM sends a direct message to userID about the given alert
// at the specified escalation tier.
SendEscalationDM(userID string, alert *models.Alert, tierIndex int) error
}
EscalationNotifier sends notifications to on-call users during escalation. The worker implements this via the ChatService.
type GroupingAction ¶
type GroupingAction string
GroupingAction defines the possible grouping actions
const ( // GroupActionCreateNew means create a new incident for this alert GroupActionCreateNew GroupingAction = "create_new" // GroupActionLinkToExisting means link this alert to an existing incident GroupActionLinkToExisting GroupingAction = "link_to_existing" // GroupActionDefault means no rule matched, use default behavior (create new) GroupActionDefault GroupingAction = "default" )
type GroupingDecision ¶
type GroupingDecision struct {
// Action specifies what to do with this alert
Action GroupingAction
// IncidentID is the existing incident to link to (only set if Action = GroupActionLinkToExisting)
IncidentID *uuid.UUID
// RuleName is the name of the rule that matched (empty for default action)
RuleName string
// GroupKey is the derived key for this alert group
GroupKey string
}
GroupingDecision represents the result of evaluating an alert against grouping rules
type GroupingEngine ¶
type GroupingEngine interface {
// EvaluateAlert determines if an alert should create a new incident or link to existing
EvaluateAlert(alert *models.Alert) (*GroupingDecision, error)
// RefreshRules reloads grouping rules from the database (called on cache expiry)
RefreshRules() error
}
GroupingEngine evaluates alerts against grouping rules to determine incident creation/linking
func NewGroupingEngine ¶
func NewGroupingEngine( ruleRepo repository.GroupingRuleRepository, incidentRepo repository.IncidentRepository, db *gorm.DB, ) GroupingEngine
NewGroupingEngine creates a new grouping engine
type HolidayService ¶
type HolidayService struct {
// contains filtered or unexported fields
}
HolidayService fetches public holidays from ICS feeds and syncs them to the DB.
func NewHolidayService ¶
func NewHolidayService(repo repository.ScheduleRepository) *HolidayService
NewHolidayService creates a new HolidayService.
func (*HolidayService) SyncAll ¶
func (s *HolidayService) SyncAll()
SyncAll refreshes holidays for every schedule that has holiday countries configured.
func (*HolidayService) SyncSchedule ¶
func (s *HolidayService) SyncSchedule(scheduleID uuid.UUID, countries []string) error
SyncSchedule fetches holidays for the given countries and upserts them for scheduleID. Countries not in supportedCountries are silently skipped.
type IncidentService ¶
type IncidentService interface {
// Alert-triggered incident creation
CreateIncidentFromAlert(alert *models.Alert, aiEnabled bool) (*models.Incident, error)
CreateIncidentFromAlertWithGrouping(alert *models.Alert, groupKey string, aiEnabled bool) (*models.Incident, error)
LinkAlertToExistingIncident(alert *models.Alert, incidentID uuid.UUID) error
ShouldCreateIncident(severity models.AlertSeverity) bool
CreateSlackChannelForIncident(incident *models.Incident, alerts []models.Alert) error
// API operations
ListIncidents(filters repository.IncidentFilters, pagination repository.Pagination) ([]models.Incident, int64, error)
GetIncident(id uuid.UUID, number int) (*models.Incident, error)
GetIncidentBySlackChannelID(channelID string) (*models.Incident, error)
GetIncidentBySlackMessageTS(messageTS string) (*models.Incident, error)
CreateIncident(params *CreateIncidentParams) (*models.Incident, error)
UpdateIncident(id uuid.UUID, params *UpdateIncidentParams) (*models.Incident, error)
AcknowledgeIncident(id uuid.UUID, actorType, actorID string) error
ResolveIncident(id uuid.UUID, actorType, actorID string) error
GetIncidentAlerts(incidentID uuid.UUID) ([]models.Alert, error)
GetIncidentTimeline(incidentID uuid.UUID, pagination repository.Pagination) ([]models.TimelineEntry, int64, error)
CreateTimelineEntry(params *CreateTimelineEntryParams) (*models.TimelineEntry, error)
// Generic status transition used by bots/integrations (e.g. Slack reactions).
UpdateIncidentStatus(id uuid.UUID, status models.IncidentStatus, actorType, actorID string) error
// Slack notifications
PostStatusUpdateToSlack(incident *models.Incident, previousStatus, newStatus models.IncidentStatus) error
// AI features (v0.6+)
// GenerateAISummary generates an AI summary for an incident and persists it.
GenerateAISummary(incident *models.Incident) (string, error)
// GenerateHandoffDigest generates a shift handoff digest for an incident (not persisted).
GenerateHandoffDigest(incident *models.Incident) (string, error)
}
IncidentService defines the interface for incident operations
func NewIncidentService ¶
func NewIncidentService( incidentRepo repository.IncidentRepository, timelineRepo repository.TimelineRepository, alertRepo repository.AlertRepository, chatService ChatService, db *gorm.DB, ) IncidentService
NewIncidentService creates a new incident service
type LocalAuthService ¶
type LocalAuthService interface {
Login(email, password string) (*models.LocalSession, error)
Logout(token string) error
GetSessionUser(token string) (*models.User, error)
CreateUser(email, name, password string, role models.UserRole) (*models.User, string, error)
UpdateUser(id uuid.UUID, name string, role models.UserRole, newPassword string) error
ResetPassword(id uuid.UUID) (string, error)
DeactivateUser(id uuid.UUID) error
GetUser(id uuid.UUID) (*models.User, error)
ListUsers() ([]models.User, error)
CountUsers() (int64, error)
CountAdmins() (int64, error)
// GetUserByEmail returns the user with the given email, regardless of auth source.
// Used by middleware to resolve a SAML JWT session to a DB user record.
GetUserByEmail(email string) (*models.User, error)
// LoginByUserID creates a session for the given user ID without a password check.
// Used by Slack OAuth login after the user's identity is verified by Slack.
LoginByUserID(id uuid.UUID) (*models.LocalSession, error)
// UpdateUserSlackID sets or clears the Slack member ID for a user.
// Pass nil to clear, or a pointer to an ID string like "U0AJLLY3678".
UpdateUserSlackID(id uuid.UUID, slackUserID *string) error
// UpdateUserTeamsID sets or clears the Azure AD Object ID for a user.
// Pass nil to clear, or a pointer to the AAD Object ID string.
UpdateUserTeamsID(id uuid.UUID, teamsUserID *string) error
}
LocalAuthService handles local email/password authentication.
func NewLocalAuthService ¶
func NewLocalAuthService(users repository.UserRepository, sessions repository.LocalSessionRepository) LocalAuthService
type Message ¶
type Message struct {
// Text is the plain text fallback content
// Required for notifications and accessibility
Text string
// Blocks contains platform-specific rich content blocks
// For Slack: []slack.Block
// For Teams: []teams.MessageBlock
// Stored as []interface{} to remain platform-agnostic
Blocks []interface{}
// ThreadTS is the parent message timestamp for threaded replies
// Empty string for top-level messages
ThreadTS string
}
Message represents a chat message with platform-agnostic content. Implementations should convert Blocks to their platform's format.
type PostMortemService ¶
type PostMortemService interface {
// Templates
ListTemplates() ([]models.PostMortemTemplate, error)
GetTemplate(id uuid.UUID) (*models.PostMortemTemplate, error)
CreateTemplate(params *CreatePostMortemTemplateParams) (*models.PostMortemTemplate, error)
UpdateTemplate(id uuid.UUID, params *UpdatePostMortemTemplateParams) (*models.PostMortemTemplate, error)
DeleteTemplate(id uuid.UUID) error
// Post-mortems
GetPostMortem(incidentID uuid.UUID) (*models.PostMortem, error)
GeneratePostMortem(incident *models.Incident, templateID *uuid.UUID, createdByID string) (*models.PostMortem, error)
UpdatePostMortem(id uuid.UUID, params *UpdatePostMortemParams) (*models.PostMortem, error)
// Post-mortem manual creation
CreatePostMortem(incidentID uuid.UUID, createdByID string) (*models.PostMortem, error)
// AI enhance
EnhancePostMortem(pm *models.PostMortem, content string) (*models.PostMortem, error)
// Comments
ListComments(postMortemID uuid.UUID) ([]models.PostMortemComment, error)
CreateComment(postMortemID uuid.UUID, params *CreateCommentParams) (*models.PostMortemComment, error)
DeleteComment(id uuid.UUID) error
// Action items
CreateActionItem(postMortemID uuid.UUID, params *CreateActionItemParams) (*models.ActionItem, error)
UpdateActionItem(id uuid.UUID, params *UpdateActionItemParams) (*models.ActionItem, error)
DeleteActionItem(id uuid.UUID) error
}
PostMortemService manages post-mortem templates, documents, and action items.
func NewPostMortemService ¶
func NewPostMortemService( pmRepo repository.PostMortemRepository, templateRepo repository.PostMortemTemplateRepository, commentRepo repository.PostMortemCommentRepository, incidentSvc IncidentService, aiSvc AIService, ) PostMortemService
type ProcessingResult ¶
type ProcessingResult struct {
Received int // Total alerts received in payload
Created int // New alerts created
Updated int // Existing alerts updated
IncidentsCreated int // New incidents created from alerts
}
ProcessingResult holds statistics about webhook processing
type RoutingDecision ¶
type RoutingDecision struct {
// Suppress means: store the alert but do not create an incident
Suppress bool
// SeverityOverride replaces the alert's severity for incident creation (empty = no override)
SeverityOverride string
// ChannelOverride replaces the auto-generated Slack channel name suffix (empty = no override)
ChannelOverride string
// RuleName is the name of the rule that matched (empty if no rule matched)
RuleName string
// EscalationPolicyID is the escalation policy to trigger for this alert (nil = no escalation).
// Set from the "escalation_policy_id" key in the matching rule's actions JSONB.
EscalationPolicyID *uuid.UUID
// AIEnabled controls whether AI agents process the resulting incident.
// Defaults to true; can be set false via the "ai_enabled" key in the rule's actions JSONB.
AIEnabled bool
}
RoutingDecision represents the result of evaluating an alert against routing rules
type RoutingEngine ¶
type RoutingEngine interface {
// EvaluateAlert returns the routing decision for a given alert
EvaluateAlert(alert *models.Alert) (*RoutingDecision, error)
// RefreshRules reloads routing rules from the database
RefreshRules() error
}
RoutingEngine evaluates alerts against routing rules to determine incident routing behavior
func NewRoutingEngine ¶
func NewRoutingEngine(ruleRepo repository.RoutingRuleRepository) RoutingEngine
NewRoutingEngine creates a new routing engine
type ScheduleEvaluator ¶
type ScheduleEvaluator interface {
// WhoIsOnCall returns the username on call for the given schedule at the given time.
// Returns ("", nil) if the schedule has no layers/participants configured.
WhoIsOnCall(scheduleID uuid.UUID, at time.Time) (string, error)
// GetTimeline returns the on-call schedule broken into contiguous segments
// for the window [from, to). Defaults to next 7 days if from/to are zero.
GetTimeline(scheduleID uuid.UUID, from, to time.Time) ([]TimelineSegment, error)
// GetLayerTimelines returns per-layer timelines and the effective merged timeline.
// layerTimelines is keyed by layer UUID. Each layer is computed independently
// (no override application). effective is the fully-merged timeline (same as GetTimeline).
GetLayerTimelines(scheduleID uuid.UUID, from, to time.Time) (layerTimelines map[uuid.UUID][]TimelineSegment, effective []TimelineSegment, err error)
}
ScheduleEvaluator computes who is on call at any given time.
func NewScheduleEvaluator ¶
func NewScheduleEvaluator(repo repository.ScheduleRepository) ScheduleEvaluator
NewScheduleEvaluator creates a new schedule evaluator.
type SlackEventHandler ¶
type SlackEventHandler struct {
// contains filtered or unexported fields
}
SlackEventHandler listens for Slack events via Socket Mode (WebSocket) and dispatches them to the appropriate handlers. Socket Mode avoids needing a public URL or SSL certificate — it uses an outbound WebSocket connection.
func NewSlackEventHandler ¶
func NewSlackEventHandler( appToken string, botToken string, incidentService IncidentService, chatService ChatService, userRepo repository.UserRepository, pmRepo repository.PostMortemRepository, ) (*SlackEventHandler, error)
NewSlackEventHandler creates a Socket Mode event handler. Requires both SLACK_APP_TOKEN (xapp-...) and SLACK_BOT_TOKEN (xoxb-...).
func (*SlackEventHandler) SetAIService ¶
func (h *SlackEventHandler) SetAIService(ai AIService)
SetAIService wires an optional AI service into the handler for @mention responses.
func (*SlackEventHandler) Start ¶
func (h *SlackEventHandler) Start()
Start begins the Socket Mode connection and event listener in background goroutines. It returns immediately; events are processed asynchronously.
type SlackMessageBuilder ¶
type SlackMessageBuilder struct{}
SlackMessageBuilder constructs rich Slack messages using Block Kit.
func NewSlackMessageBuilder ¶
func NewSlackMessageBuilder() *SlackMessageBuilder
NewSlackMessageBuilder creates a new SlackMessageBuilder instance.
func (*SlackMessageBuilder) BuildAlertLinkedMessage ¶
func (b *SlackMessageBuilder) BuildAlertLinkedMessage(alert *models.Alert, incident *models.Incident) Message
BuildIncidentUpdatedMessage rebuilds the incident message with status-aware buttons. Used to update the original pinned message when status changes from any source.
BuildAlertLinkedMessage creates a message for when an alert is linked to an existing incident. This is used for grouped alerts (v0.3+) to notify that a new alert has been added to the incident.
func (*SlackMessageBuilder) BuildEscalationDMMessage ¶
func (b *SlackMessageBuilder) BuildEscalationDMMessage(alert *models.Alert, tierIndex int) Message
BuildEscalationDMMessage creates the Slack DM sent to an on-call user when an alert escalates to their tier. Includes an "Acknowledge" interactive button whose action_id encodes the alert ID so the Slack event handler can route it to POST /api/v1/alerts/:id/acknowledge.
tierIndex is 0-based; the display shows "Tier N" where N = tierIndex+1.
func (*SlackMessageBuilder) BuildIncidentCreatedMessage ¶
func (b *SlackMessageBuilder) BuildIncidentCreatedMessage( incident *models.Incident, alerts []models.Alert, ) Message
BuildIncidentCreatedMessage creates a rich Block Kit message for a new incident. The message includes: - Header with severity emoji and incident title - Incident details (severity, status, created time) - Linked alerts (if any) - Action buttons (Acknowledge, Resolve)
func (*SlackMessageBuilder) BuildIncidentUpdatedMessage ¶
func (b *SlackMessageBuilder) BuildIncidentUpdatedMessage(incident *models.Incident) Message
Button rules:
- triggered: Acknowledge + Resolve
- acknowledged: Resolve only
- resolved/canceled: no buttons
func (*SlackMessageBuilder) BuildShiftChannelNotification ¶
func (b *SlackMessageBuilder) BuildShiftChannelNotification( scheduleName, layerName, outgoingUser, incomingUser string, shiftEnd time.Time, ) Message
BuildShiftChannelNotification creates a channel post announcing a shift handoff. Posted to schedule.NotificationChannel when configured.
func (*SlackMessageBuilder) BuildShiftHandoffIncomingMessage ¶
func (b *SlackMessageBuilder) BuildShiftHandoffIncomingMessage( scheduleName, layerName string, shiftEnd time.Time, ) Message
BuildShiftHandoffIncomingMessage creates a DM for the user who is starting their on-call shift.
func (*SlackMessageBuilder) BuildShiftHandoffOutgoingMessage ¶
func (b *SlackMessageBuilder) BuildShiftHandoffOutgoingMessage( scheduleName, layerName, incomingUser string, ) Message
BuildShiftHandoffOutgoingMessage creates a DM for the user whose on-call shift has ended.
func (*SlackMessageBuilder) BuildStatusUpdateMessage ¶
func (b *SlackMessageBuilder) BuildStatusUpdateMessage( incident *models.Incident, previousStatus models.IncidentStatus, newStatus models.IncidentStatus, ) Message
BuildStatusUpdateMessage creates a message for incident status changes
type SlackValidator ¶
type SlackValidator struct {
// contains filtered or unexported fields
}
SlackValidator validates Slack configuration and permissions.
func NewSlackValidator ¶
func NewSlackValidator(token string) *SlackValidator
NewSlackValidator creates a new Slack validator.
func (*SlackValidator) ValidateConfiguration ¶
func (v *SlackValidator) ValidateConfiguration() error
ValidateConfiguration performs complete validation of Slack configuration. This is a convenience method that calls both ValidateToken and ValidateScopes.
func (*SlackValidator) ValidateScopes ¶
func (v *SlackValidator) ValidateScopes() error
ValidateScopes validates that the bot has the required OAuth scopes. Since auth.test doesn't return scopes directly, we verify by attempting operations that require each scope where possible without side effects.
Required scopes: - channels:manage - Create and manage channels - channels:read - Read channel information - chat:write - Post messages
Note: channels:manage and chat:write cannot be validated without side effects (creating channels/posting messages). These scopes will be validated on first use, and the Slack API will return clear error messages if they are missing.
func (*SlackValidator) ValidateToken ¶
func (v *SlackValidator) ValidateToken() error
ValidateToken validates the Slack bot token by calling auth.test. This verifies the token is valid and active.
type TeamMember ¶
type TeamMember struct {
UserID string // AAD Object ID — stable identifier for proactive DMs (teams_user_id)
DisplayName string
Email string
}
TeamMember is a member of the Teams team as returned by the Graph API.
func ListTeamMembers ¶
func ListTeamMembers(ctx context.Context, appID, appPassword, tenantID, teamID string) ([]TeamMember, error)
ListTeamMembers fetches all members of the configured team from the Graph API. Intended for the "Import from Teams" UI flow; does not require a full TeamsService.
type TeamsEventHandler ¶
type TeamsEventHandler struct {
// contains filtered or unexported fields
}
TeamsEventHandler processes inbound Bot Framework activities from Teams. It is wired to POST /api/v1/webhooks/teams by the Gin router. JWT authentication is handled by middleware/teams_auth.go before this handler is called.
func NewTeamsEventHandler ¶
func NewTeamsEventHandler(appID string, incidentSvc IncidentService, incidentRepo repository.IncidentRepository, timelineRepo repository.TimelineRepository, teamsSvc *TeamsService) *TeamsEventHandler
NewTeamsEventHandler creates a handler for inbound Teams Bot Framework activities.
func (*TeamsEventHandler) Handle ¶
func (h *TeamsEventHandler) Handle(ctx context.Context, activity BotActivity)
Handle processes an inbound Bot Framework activity payload.
type TeamsService ¶
type TeamsService struct {
// contains filtered or unexported fields
}
TeamsService implements ChatService using Microsoft Graph API for channel management and Bot Framework REST API for posting messages to channels.
Why two clients?
Graph API (ChannelMessage.Send) only works with delegated (user) auth — app-only tokens get a 403. Bot Framework Proactive Messaging uses a separate OAuth scope (https://api.botframework.com/.default) that does work with client credentials, giving us true Slack parity: same bot, same credentials, no per-channel setup.
Client split:
graphClient — Graph API (create/archive channels, DMs, team info) botfwClient — Bot Framework REST API (post/update messages in channels)
func NewTeamsService ¶
func NewTeamsService(ctx context.Context, appID, appPassword, tenantID, teamID, botUserID, serviceURL string) (*TeamsService, error)
NewTeamsService creates a TeamsService with two authenticated HTTP clients. serviceURL is the Bot Framework relay endpoint for the tenant. It can be found in any inbound Bot Framework activity's serviceUrl field. Defaults to the US region endpoint if empty; set TEAMS_SERVICE_URL for other regions (EU: smba.trafficmanager.net/emea/).
func (*TeamsService) ArchiveChannel ¶
func (s *TeamsService) ArchiveChannel(channelID string) error
ArchiveChannel best-effort renames the channel to mark it as resolved. Graph API does not support archiving standard channels (only private channels), so this is a rename-only operation. Always returns nil — the error is logged internally. Callers should not check the return value; the incident is already resolved regardless of whether the rename succeeds.
func (*TeamsService) CreateChannel ¶
func (s *TeamsService) CreateChannel(name, description string) (*Channel, error)
func (*TeamsService) GetThreadMessages ¶
func (s *TeamsService) GetThreadMessages(channelID, threadTS string) ([]string, error)
func (*TeamsService) InviteUsers ¶
func (s *TeamsService) InviteUsers(channelID string, userIDs []string) error
func (*TeamsService) PostMessage ¶
func (s *TeamsService) PostMessage(channelID string, msg Message) (string, error)
PostMessage sends a message to a Teams channel via the ChatService interface. Internally this creates a new Bot Framework conversation in the channel and posts to it. Callers that need the conversationID for future updates should use PostToChannel instead.
func (*TeamsService) PostToChannel ¶
func (s *TeamsService) PostToChannel(teamsChannelID string, msg Message) (conversationID, activityID string, err error)
PostToChannel creates a new Bot Framework conversation in the given Teams channel and posts a message to it in a single API call. Returns both the conversationID and activityID so callers can store both for future PostToConversation and UpdateConversationMessage calls.
Use this for the initial message when creating an incident channel.
func (*TeamsService) PostToConversation ¶
func (s *TeamsService) PostToConversation(conversationID string, msg Message) (string, error)
PostToConversation posts a new message to an existing Bot Framework conversation. Use this for status updates, timeline notes, and bot replies after the initial PostToChannel has established the conversationID.
func (*TeamsService) SendDirectMessage ¶
func (s *TeamsService) SendDirectMessage(userID string, msg Message) error
func (*TeamsService) SendOnCallDM ¶
func (s *TeamsService) SendOnCallDM(aadObjectID string, incident *models.Incident, appURL string) error
SendOnCallDM sends a proactive Adaptive Card DM to the on-call responder via Bot Framework. aadObjectID is the Azure AD Object ID stored in users.teams_user_id. Unlike SendDirectMessage (which uses Graph API chat threads), this uses the Bot Framework relay so it works with the same client credentials scope — no Chat.ReadWrite delegation needed.
func (*TeamsService) UpdateConversationMessage ¶
func (s *TeamsService) UpdateConversationMessage(conversationID, activityID string, msg Message) error
UpdateConversationMessage updates an existing message in a Bot Framework conversation. Used to keep the root incident Adaptive Card in sync with the current status.
func (*TeamsService) UpdateMessage ¶
func (s *TeamsService) UpdateMessage(channelID, messageID string, msg Message) error
UpdateMessage updates an existing message. For Bot Framework updates the caller must pass the conversationID as channelID and the activityID as messageID. Used by postStatusUpdateToTeams via UpdateConversationMessage.
type TelegramService ¶
type TelegramService struct {
// contains filtered or unexported fields
}
TelegramService sends incident notification messages to a Telegram group/channel. Notification-only: no channel creation, no inbound command handling in v1.
func NewTelegramService ¶
func NewTelegramService(botToken, chatID, appURL string) *TelegramService
NewTelegramService constructs a TelegramService. Returns nil if token or chatID is empty.
func NewTelegramServiceFromConfig ¶
func NewTelegramServiceFromConfig(cfg *models.TelegramConfig, appURL string) *TelegramService
NewTelegramServiceFromConfig constructs from a DB config row. Returns nil if not configured.
func (*TelegramService) SendAISummary ¶
func (s *TelegramService) SendAISummary(incident *models.Incident, summary string) error
SendAISummary posts the AI-generated summary for an incident to Telegram.
func (*TelegramService) SendIncidentCreated ¶
func (s *TelegramService) SendIncidentCreated(incident *models.Incident) error
SendIncidentCreated posts an incident creation notification.
func (*TelegramService) SendStatusUpdate ¶
func (s *TelegramService) SendStatusUpdate(incident *models.Incident, newStatus string) error
SendStatusUpdate posts a status change notification.
type TimelineSegment ¶
type TimelineSegment struct {
// Start is the beginning of this segment (inclusive).
Start time.Time `json:"start"`
// End is the end of this segment (exclusive).
End time.Time `json:"end"`
// UserName is the on-call user for this segment.
UserName string `json:"user_name"`
// IsOverride is true when this segment is covered by an explicit override.
IsOverride bool `json:"is_override"`
// LayerID is the layer this segment belongs to. nil for effective/merged timeline.
LayerID *uuid.UUID `json:"layer_id,omitempty"`
}
TimelineSegment represents a contiguous window during which a single user is on call.
type UpdateActionItemParams ¶
type UpdateIncidentParams ¶
type UpdateIncidentParams struct {
Status models.IncidentStatus
Severity models.IncidentSeverity
Summary string
UpdatedBy string
ClientIP string // For audit logging
AIEnabled *bool // Controls whether AI agents process this incident. nil = no change.
CommanderID *uuid.UUID // nil = no change
}
UpdateIncidentParams holds parameters for updating an incident
type UpdatePostMortemParams ¶
type UpdatePostMortemParams struct {
Content *string
Status *models.PostMortemStatus
}
Source Files
¶
- acknowledge_service.go
- ai_service.go
- alert_service.go
- auth_service.go
- chat_service.go
- escalation_engine.go
- grouping_engine.go
- holiday_service.go
- incident_service.go
- lazy_slack_service.go
- local_auth_service.go
- multi_chat_service.go
- post_mortem_service.go
- routing_engine.go
- schedule_evaluator.go
- slack_event_handler.go
- slack_message_builder.go
- slack_service.go
- slack_validator.go
- teams_event_handler.go
- teams_message_builder.go
- teams_service.go
- telegram_service.go