Add nudge_channels to MessagingConfig schema (gt-3shmx)

- Add NudgeChannels field to MessagingConfig struct in types.go
- Initialize NudgeChannels map in NewMessagingConfig()
- Add validation in validateMessagingConfig(): channel names must be
  non-empty and each channel must have at least one recipient
- Add tests for valid nudge channels and empty recipient validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/polecats/toast
2025-12-30 22:47:14 -08:00
committed by Steve Yegge
parent d28ba6e2c6
commit d94fb4669b
3 changed files with 58 additions and 5 deletions

View File

@@ -629,6 +629,9 @@ func validateMessagingConfig(c *MessagingConfig) error {
if c.Announces == nil {
c.Announces = make(map[string]AnnounceConfig)
}
if c.NudgeChannels == nil {
c.NudgeChannels = make(map[string][]string)
}
// Validate lists have at least one recipient
for name, recipients := range c.Lists {
@@ -657,6 +660,16 @@ func validateMessagingConfig(c *MessagingConfig) error {
}
}
// Validate nudge channels have non-empty names and at least one recipient
for name, recipients := range c.NudgeChannels {
if name == "" {
return fmt.Errorf("%w: nudge channel name cannot be empty", ErrMissingField)
}
if len(recipients) == 0 {
return fmt.Errorf("%w: nudge channel '%s' has no recipients", ErrMissingField, name)
}
}
return nil
}

View File

@@ -566,6 +566,8 @@ func TestMessagingConfigRoundTrip(t *testing.T) {
Readers: []string{"@town"},
RetainCount: 100,
}
original.NudgeChannels["workers"] = []string{"gastown/polecats/*", "gastown/crew/*"}
original.NudgeChannels["witnesses"] = []string{"*/witness"}
if err := SaveMessagingConfig(path, original); err != nil {
t.Fatalf("SaveMessagingConfig: %v", err)
@@ -606,6 +608,17 @@ func TestMessagingConfigRoundTrip(t *testing.T) {
if a, ok := loaded.Announces["alerts"]; !ok || a.RetainCount != 100 {
t.Error("announce not preserved")
}
// Check nudge channels
if len(loaded.NudgeChannels) != 2 {
t.Errorf("NudgeChannels count = %d, want 2", len(loaded.NudgeChannels))
}
if workers, ok := loaded.NudgeChannels["workers"]; !ok || len(workers) != 2 {
t.Error("workers nudge channel not preserved")
}
if witnesses, ok := loaded.NudgeChannels["witnesses"]; !ok || len(witnesses) != 1 {
t.Error("witnesses nudge channel not preserved")
}
}
func TestMessagingConfigValidation(t *testing.T) {
@@ -696,6 +709,27 @@ func TestMessagingConfigValidation(t *testing.T) {
},
wantErr: true,
},
{
name: "valid config with nudge channels",
config: &MessagingConfig{
Type: "messaging",
Version: 1,
NudgeChannels: map[string][]string{
"workers": {"gastown/polecats/*", "gastown/crew/*"},
},
},
wantErr: false,
},
{
name: "nudge channel with no recipients",
config: &MessagingConfig{
Version: 1,
NudgeChannels: map[string][]string{
"empty": {},
},
},
wantErr: true,
},
}
for _, tt := range tests {

View File

@@ -278,6 +278,11 @@ type MessagingConfig struct {
// Used for broadcast announcements that don't need acknowledgment.
// Example: {"alerts": {"readers": ["@town"]}}
Announces map[string]AnnounceConfig `json:"announces,omitempty"`
// NudgeChannels are named groups for real-time nudge fan-out.
// Like mailing lists but for tmux send-keys instead of durable mail.
// Example: {"workers": ["gastown/polecats/*", "gastown/crew/*"], "witnesses": ["*/witness"]}
NudgeChannels map[string][]string `json:"nudge_channels,omitempty"`
}
// QueueConfig represents a work queue configuration.
@@ -306,10 +311,11 @@ const CurrentMessagingVersion = 1
// NewMessagingConfig creates a new MessagingConfig with defaults.
func NewMessagingConfig() *MessagingConfig {
return &MessagingConfig{
Type: "messaging",
Version: CurrentMessagingVersion,
Lists: make(map[string][]string),
Queues: make(map[string]QueueConfig),
Announces: make(map[string]AnnounceConfig),
Type: "messaging",
Version: CurrentMessagingVersion,
Lists: make(map[string][]string),
Queues: make(map[string]QueueConfig),
Announces: make(map[string]AnnounceConfig),
NudgeChannels: make(map[string][]string),
}
}