- Rename Go module switchboard-core → armature (155+ files) - Rename Docker image → gobha/armature - Rename K8s resources, secrets, deployments - Rename Prometheus metrics switchboard_* → armature_* - Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_* - Rename DB names switchboard_core* → armature* - Update all frontend branding, notification templates, docs - Update CI scripts, e2e tests, Keycloak realm, nginx conf - Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh - Rename k8s/switchboard.yaml → k8s/armature.yaml - Rename chart alerting/dashboard files - Fix: DockerHub push uses env: binding for secret injection - Helm chart updated (name, labels, template functions, dashboard, alerting) - Replace favicon/icon assets with Armature brand No functional changes. Pure mechanical rename + CI fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
186 lines
6.1 KiB
Go
186 lines
6.1 KiB
Go
package notifications
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"armature/auth"
|
|
"armature/events"
|
|
"armature/models"
|
|
"armature/store"
|
|
)
|
|
|
|
// ── Role Fallback ───────────────────────────
|
|
// Subscribes to the existing "role.fallback" EventBus event (emitted by
|
|
// capabilities/resolver.go) and creates notifications for
|
|
// all admin users.
|
|
|
|
// roleFallbackPayload matches the payload emitted by the role resolver.
|
|
type roleFallbackPayload struct {
|
|
Role string `json:"role"`
|
|
Primary string `json:"primary_model"`
|
|
Fallback string `json:"fallback_model"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// RoleFallbackHandler returns an events.Handler that creates admin
|
|
// notifications when a model role falls back.
|
|
func RoleFallbackHandler(svc *Service, stores store.Stores) events.Handler {
|
|
return func(e events.Event) {
|
|
var p roleFallbackPayload
|
|
if err := json.Unmarshal(e.Payload, &p); err != nil {
|
|
log.Printf("[notifications] failed to parse role.fallback payload: %v", err)
|
|
return
|
|
}
|
|
|
|
title := fmt.Sprintf("Model fallback: %s → %s", p.Primary, p.Fallback)
|
|
if p.Primary == "" {
|
|
title = fmt.Sprintf("Role '%s' fell back to %s", p.Role, p.Fallback)
|
|
}
|
|
|
|
body := ""
|
|
if p.Error != "" {
|
|
body = fmt.Sprintf("Primary model error: %s", p.Error)
|
|
}
|
|
|
|
// Notify all Admins group members
|
|
ctx := context.Background()
|
|
admins, err := stores.Groups.ListMembers(ctx, auth.AdminsGroupID)
|
|
if err != nil {
|
|
log.Printf("[notifications] failed to list admins for role.fallback: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, u := range admins {
|
|
n := models.Notification{
|
|
UserID: u.UserID,
|
|
Type: models.NotifTypeRoleFallback,
|
|
Title: title,
|
|
Body: body,
|
|
}
|
|
if err := svc.Notify(ctx, &n); err != nil {
|
|
log.Printf("[notifications] failed to create role.fallback notification for %s: %v", u.UserID, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Knowledge Base ──────────────────────────
|
|
// Called directly from the KB handler after indexing completes or fails.
|
|
// Not a bus subscriber — the KB handler calls these functions.
|
|
|
|
// NotifyKBReady creates a notification when a knowledge base finishes indexing.
|
|
func NotifyKBReady(svc *Service, userID, kbID, kbName string, chunkCount int) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: userID,
|
|
Type: models.NotifTypeKBReady,
|
|
Title: fmt.Sprintf("Knowledge base \"%s\" ready (%d chunks)", kbName, chunkCount),
|
|
ResourceType: models.ResourceTypeKnowledgeBase,
|
|
ResourceID: kbID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] kb.ready failed for user %s: %v", userID, err)
|
|
}
|
|
}
|
|
|
|
// NotifyKBError creates a notification when KB indexing fails.
|
|
func NotifyKBError(svc *Service, userID, kbID, kbName, errMsg string) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: userID,
|
|
Type: models.NotifTypeKBError,
|
|
Title: fmt.Sprintf("Knowledge base \"%s\" indexing failed", kbName),
|
|
Body: errMsg,
|
|
ResourceType: models.ResourceTypeKnowledgeBase,
|
|
ResourceID: kbID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] kb.error failed for user %s: %v", userID, err)
|
|
}
|
|
}
|
|
|
|
// ── Group Membership ────────────────────────
|
|
// Called directly from the group handler on add/remove.
|
|
|
|
// NotifyGroupMemberAdded creates a notification when a user is added to a group.
|
|
func NotifyGroupMemberAdded(svc *Service, userID, groupID, groupName string) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: userID,
|
|
Type: models.NotifTypeGrantChanged,
|
|
Title: fmt.Sprintf("You were added to group \"%s\"", groupName),
|
|
ResourceType: models.ResourceTypeGroup,
|
|
ResourceID: groupID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] grant.changed (add) failed for user %s: %v", userID, err)
|
|
}
|
|
}
|
|
|
|
// NotifyGroupMemberRemoved creates a notification when a user is removed from a group.
|
|
func NotifyGroupMemberRemoved(svc *Service, userID, groupID, groupName string) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: userID,
|
|
Type: models.NotifTypeGrantChanged,
|
|
Title: fmt.Sprintf("You were removed from group \"%s\"", groupName),
|
|
ResourceType: models.ResourceTypeGroup,
|
|
ResourceID: groupID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] grant.changed (remove) failed for user %s: %v", userID, err)
|
|
}
|
|
}
|
|
|
|
// ── User Mention ───────────────────────────
|
|
// Called from handlers/completion.go when an @mention targets a human user.
|
|
|
|
// NotifyUserMentioned creates a persisted notification for an @mention.
|
|
func NotifyUserMentioned(svc *Service, mentionedUserID, channelID, fromUserID, preview string) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: mentionedUserID,
|
|
Type: models.NotifTypeUserMentioned,
|
|
Title: "You were mentioned in a conversation",
|
|
Body: preview,
|
|
ResourceType: models.ResourceTypeChannel,
|
|
ResourceID: channelID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] user.mentioned failed for user %s: %v", mentionedUserID, err)
|
|
}
|
|
}
|
|
|
|
// ── Workflow Claimed ───────────────────────
|
|
// Called from handlers/workflow_assignments.go when a user claims an assignment.
|
|
|
|
// NotifyWorkflowClaimed creates a persisted notification when an assignment is claimed.
|
|
func NotifyWorkflowClaimed(svc *Service, claimerID, assignmentID, channelID string) {
|
|
if svc == nil {
|
|
return
|
|
}
|
|
n := models.Notification{
|
|
UserID: claimerID,
|
|
Type: models.NotifTypeWorkflowClaimed,
|
|
Title: "You claimed a workflow assignment",
|
|
ResourceType: models.ResourceTypeChannel,
|
|
ResourceID: channelID,
|
|
}
|
|
if err := svc.Notify(context.Background(), &n); err != nil {
|
|
log.Printf("[notifications] workflow.claimed failed for user %s: %v", claimerID, err)
|
|
}
|
|
}
|