Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

Technical Specification - Enhanced State Management System

Table of Contents

  1. System Architecture
  2. Data Models
  3. API Specifications
  4. File Format Specifications
  5. State Machine Definitions
  6. Event Processing
  7. Performance Requirements
  8. Security Considerations
  9. Error Handling
  10. Monitoring and Observability

System Architecture

Component Overview

┌─────────────────────────────────────────────────────────────┐
│                    SoundTouch Service                       │
├─────────────────────────────────────────────────────────────┤
│  HTTP Router & Middleware                                   │
│  ├── Mirror Middleware (Enhanced)                           │
│  ├── Recorder Middleware                                    │
│  ├── Disparity Detection                                    │
│  └── Health Check Middleware                               │
├─────────────────────────────────────────────────────────────┤
│  Service Layer                                              │
│  ├── Account Manager          ├── Lifecycle Manager        │
│  ├── Migration Controller     ├── Data Source Router       │
│  ├── Event Processor         ├── Health Monitor           │
│  └── Export Manager          └── Analytics Engine         │
├─────────────────────────────────────────────────────────────┤
│  Data Layer                                                 │
│  ├── Enhanced DataStore      ├── Event Store              │
│  ├── Mirror Cache           ├── Metrics Store             │
│  └── Configuration Store    └── Session Store             │
├─────────────────────────────────────────────────────────────┤
│  External Integrations                                      │
│  ├── Bose Services (Mirror)  ├── Device Discovery         │
│  ├── BMX/TuneIn Services    └── SSH/Setup Manager         │
└─────────────────────────────────────────────────────────────┘

Package Structure

pkg/service/
├── account/               # Account management
│   ├── manager.go
│   ├── persistence.go
│   └── validation.go
├── lifecycle/             # Device lifecycle management
│   ├── manager.go
│   ├── states.go
│   ├── transitions.go
│   └── events.go
├── migration/            # Enhanced migration (extends existing)
│   ├── controller.go
│   ├── strategies.go
│   └── progress.go
├── datasource/           # Data source routing
│   ├── router.go
│   ├── preferences.go
│   └── fallback.go
├── events/               # Event processing system
│   ├── processor.go
│   ├── queue.go
│   └── storage.go
├── mirror/               # Enhanced mirroring (extends existing)
│   ├── disparity.go
│   ├── analyzer.go
│   └── logger.go
├── health/               # System monitoring
│   ├── monitor.go
│   ├── metrics.go
│   └── alerts.go
└── export/               # Data export and backup
    ├── exporter.go
    ├── formats.go
    └── backup.go

Data Models

Account Model

type Account struct {
    ID             string           `json:"id"`
    Name           string           `json:"name"`
    Email          string           `json:"email,omitempty"`
    CreatedAt      time.Time        `json:"created_at"`
    UpdatedAt      time.Time        `json:"updated_at"`
    Status         AccountStatus    `json:"status"`
    DeviceCount    int              `json:"device_count"`
    MigrationInfo  *MigrationInfo   `json:"migration_info,omitempty"`
    BoseAccountID  string           `json:"bose_account_id,omitempty"`
    DataSources    DataSourceConfig `json:"data_sources"`
    Settings       AccountSettings  `json:"settings"`
}

type AccountStatus string
const (
    AccountStatusActive    AccountStatus = "active"
    AccountStatusMigrating AccountStatus = "migrating"
    AccountStatusSuspended AccountStatus = "suspended"
    AccountStatusArchived  AccountStatus = "archived"
)

type MigrationInfo struct {
    StartedAt        time.Time `json:"started_at"`
    CompletedAt      *time.Time `json:"completed_at,omitempty"`
    DevicesMigrated  int       `json:"devices_migrated"`
    DevicesPending   int       `json:"devices_pending"`
    MirrorActive     bool      `json:"mirror_active"`
    Strategy         string    `json:"strategy"`
    RollbackData     string    `json:"rollback_data,omitempty"`
}

type DataSourceConfig struct {
    Local       bool   `json:"local"`
    BoseMirror  bool   `json:"bose_mirror"`
    Primary     string `json:"primary"` // "local" or "bose"
}

type AccountSettings struct {
    AutoMigration    bool     `json:"auto_migration"`
    MirrorEndpoints  []string `json:"mirror_endpoints"`
    RetentionDays    int      `json:"retention_days"`
}

Device Lifecycle Model

type DeviceLifecycle struct {
    DeviceID     string            `json:"device_id"`
    AccountID    string            `json:"account_id"`
    State        DeviceState       `json:"state"`
    CreatedAt    time.Time         `json:"created_at"`
    UpdatedAt    time.Time         `json:"updated_at"`
    StateHistory []StateTransition `json:"state_history"`
    Metadata     DeviceMetadata    `json:"metadata"`
    DataSources  DataSourceConfig  `json:"data_sources"`
    Migration    *DeviceMigration  `json:"migration,omitempty"`
    Health       DeviceHealth      `json:"health"`
}

type DeviceState string
const (
    DeviceStateUnregistered DeviceState = "unregistered"
    DeviceStateDiscovered   DeviceState = "discovered"
    DeviceStateRegistering  DeviceState = "registering"
    DeviceStateActive       DeviceState = "active"
    DeviceStateMigrating    DeviceState = "migrating"
    DeviceStateOffline      DeviceState = "offline"
    DeviceStateError        DeviceState = "error"
    DeviceStateRetired      DeviceState = "retired"
)

type StateTransition struct {
    From      DeviceState `json:"from"`
    To        DeviceState `json:"to"`
    Timestamp time.Time   `json:"timestamp"`
    Reason    string      `json:"reason"`
    Source    string      `json:"source"`
    Context   map[string]interface{} `json:"context,omitempty"`
}

type DeviceMetadata struct {
    Name            string    `json:"name"`
    Type            string    `json:"type"`
    SerialNumber    string    `json:"serial_number"`
    FirmwareVersion string    `json:"firmware_version"`
    MACAddress      string    `json:"mac_address"`
    IPAddress       string    `json:"ip_address"`
    LastSeen        time.Time `json:"last_seen"`
    IsLegacyID      bool      `json:"is_legacy_id"`
    Capabilities    []string  `json:"capabilities,omitempty"`
}

type DeviceMigration struct {
    FromBoseAccount string     `json:"from_bose_account,omitempty"`
    MigratedAt      *time.Time `json:"migrated_at,omitempty"`
    Method          string     `json:"method"`
    RollbackAvailable bool     `json:"rollback_available"`
    DataPreserved   []string   `json:"data_preserved"`
}

type DeviceHealth struct {
    Status          string    `json:"status"` // "healthy", "warning", "error"
    LastCheck       time.Time `json:"last_check"`
    ResponseTime    int       `json:"response_time_ms"`
    Connectivity    string    `json:"connectivity"` // "online", "offline", "intermittent"
    ErrorCount      int       `json:"error_count"`
    LastError       string    `json:"last_error,omitempty"`
}

Event Model

type DeviceEvent struct {
    ID        string                 `json:"id"`
    DeviceID  string                 `json:"device_id"`
    AccountID string                 `json:"account_id"`
    Type      DeviceEventType        `json:"type"`
    Data      map[string]interface{} `json:"data"`
    Timestamp time.Time              `json:"timestamp"`
    Source    EventSource            `json:"source"`
    Processed bool                   `json:"processed"`
    Context   EventContext           `json:"context"`
}

type DeviceEventType string
const (
    EventTypeNowPlaying      DeviceEventType = "now_playing"
    EventTypePresetChanged   DeviceEventType = "preset_changed"
    EventTypeVolumeChanged   DeviceEventType = "volume_changed"
    EventTypeSourceChanged   DeviceEventType = "source_changed"
    EventTypeDeviceOnline    DeviceEventType = "device_online"
    EventTypeDeviceOffline   DeviceEventType = "device_offline"
    EventTypeZoneChanged     DeviceEventType = "zone_changed"
    EventTypeDisparityFound  DeviceEventType = "disparity_found"
    EventTypeMigrationStart  DeviceEventType = "migration_start"
    EventTypeMigrationEnd    DeviceEventType = "migration_end"
    EventTypeHealthCheck     DeviceEventType = "health_check"
    EventTypeErrorOccurred   DeviceEventType = "error_occurred"
)

type EventSource string
const (
    EventSourceWebSocket  EventSource = "websocket"
    EventSourceDiscovery  EventSource = "discovery"
    EventSourceMirror     EventSource = "mirror"
    EventSourceSystem     EventSource = "system"
    EventSourceAPI        EventSource = "api"
    EventSourceUser       EventSource = "user"
)

type EventContext struct {
    RequestID    string                 `json:"request_id,omitempty"`
    UserAgent    string                 `json:"user_agent,omitempty"`
    IPAddress    string                 `json:"ip_address,omitempty"`
    Endpoint     string                 `json:"endpoint,omitempty"`
    Additional   map[string]interface{} `json:"additional,omitempty"`
}

Disparity Model

type Disparity struct {
    ID          string                 `json:"id"`
    Timestamp   time.Time              `json:"timestamp"`
    DeviceID    string                 `json:"device_id"`
    AccountID   string                 `json:"account_id"`
    Endpoint    string                 `json:"endpoint"`
    Type        DisparityType          `json:"type"`
    Severity    DisparitySeverity      `json:"severity"`
    LocalHash   string                 `json:"local_hash"`
    UpstreamHash string                `json:"upstream_hash"`
    Details     DisparityDetails       `json:"details"`
    Context     map[string]interface{} `json:"context"`
    Resolved    bool                   `json:"resolved"`
}

type DisparityType string
const (
    DisparityTypeContentMismatch  DisparityType = "content_mismatch"
    DisparityTypeStructureDiff    DisparityType = "structure_diff"
    DisparityTypeTimestampFormat  DisparityType = "timestamp_format"
    DisparityTypeFieldMissing     DisparityType = "field_missing"
    DisparityTypeValueMismatch    DisparityType = "value_mismatch"
    DisparityTypeCountMismatch    DisparityType = "count_mismatch"
)

type DisparitySeverity string
const (
    DisparitySeverityLow      DisparitySeverity = "low"
    DisparitySeverityMedium   DisparitySeverity = "medium"
    DisparitySeverityHigh     DisparitySeverity = "high"
    DisparitySeverityCritical DisparitySeverity = "critical"
)

type DisparityDetails struct {
    FieldPath     string      `json:"field_path"`
    LocalValue    interface{} `json:"local_value"`
    UpstreamValue interface{} `json:"upstream_value"`
    Description   string      `json:"description"`
}

API Specifications

Account Management APIs

Create Account

POST /api/v1/accounts
Content-Type: application/json

{
  "name": "User Account",
  "email": "user@example.com",
  "settings": {
    "auto_migration": false,
    "retention_days": 30
  }
}

Response: 201 Created
{
  "id": "acc_12345",
  "name": "User Account",
  "email": "user@example.com",
  "created_at": "2024-01-20T10:00:00Z",
  "status": "active",
  "device_count": 0
}

Get Account

GET /api/v1/accounts/{account_id}

Response: 200 OK
{
  "id": "acc_12345",
  "name": "User Account",
  "status": "active",
  "device_count": 2,
  "migration_info": {
    "started_at": "2024-01-18T09:00:00Z",
    "devices_migrated": 1,
    "devices_pending": 1,
    "mirror_active": true
  },
  "data_sources": {
    "local": true,
    "bose_mirror": true,
    "primary": "bose"
  }
}

List Accounts

GET /api/v1/accounts?status=active&limit=10&offset=0

Response: 200 OK
{
  "accounts": [...],
  "total": 5,
  "limit": 10,
  "offset": 0
}

Device Lifecycle APIs

Register Device

POST /api/v1/accounts/{account_id}/devices
Content-Type: application/json

{
  "device_id": "A81B6A536A98",
  "name": "Living Room Speaker",
  "registration_type": "fresh"
}

Response: 201 Created
{
  "device_id": "A81B6A536A98",
  "account_id": "acc_12345",
  "state": "registering",
  "created_at": "2024-01-20T10:00:00Z"
}

Get Device State

GET /api/v1/accounts/{account_id}/devices/{device_id}/state

Response: 200 OK
{
  "device_id": "A81B6A536A98",
  "account_id": "acc_12345",
  "state": "active",
  "metadata": {
    "name": "Living Room Speaker",
    "type": "SoundTouch 30",
    "last_seen": "2024-01-20T15:30:00Z"
  },
  "health": {
    "status": "healthy",
    "connectivity": "online",
    "response_time": 45
  }
}

Migrate Device

POST /api/v1/accounts/{account_id}/devices/{device_id}/migrate
Content-Type: application/json

{
  "from_bose_account": "bose-acc-xyz",
  "preserve_data": true,
  "method": "gradual"
}

Response: 202 Accepted
{
  "migration_id": "mig_67890",
  "status": "started",
  "estimated_completion": "2024-01-20T11:00:00Z"
}

Event APIs

Get Device Events

GET /api/v1/accounts/{account_id}/devices/{device_id}/events?since=2024-01-20T00:00:00Z&type=now_playing&limit=50

Response: 200 OK
{
  "events": [
    {
      "id": "evt_12345",
      "type": "now_playing",
      "timestamp": "2024-01-20T15:30:00Z",
      "data": {
        "source": "SPOTIFY",
        "track": "Song Name",
        "artist": "Artist Name"
      }
    }
  ],
  "total": 125,
  "has_more": true
}

Stream Events

GET /api/v1/accounts/{account_id}/devices/{device_id}/events/stream
Accept: text/event-stream

Response: 200 OK
Content-Type: text/event-stream

data: {"id":"evt_12346","type":"volume_changed","timestamp":"2024-01-20T15:31:00Z","data":{"volume":50}}

data: {"id":"evt_12347","type":"now_playing","timestamp":"2024-01-20T15:32:00Z","data":{"source":"TUNEIN"}}

Monitoring APIs

System Health

GET /api/v1/system/health

Response: 200 OK
{
  "status": "healthy",
  "timestamp": "2024-01-20T15:30:00Z",
  "services": {
    "account_manager": "healthy",
    "lifecycle_manager": "healthy",
    "event_processor": "healthy",
    "mirror_service": "warning"
  },
  "statistics": {
    "total_accounts": 5,
    "total_devices": 12,
    "active_devices": 10,
    "events_processed_24h": 1547
  }
}

Disparity Analysis

GET /api/v1/system/disparities?since=2024-01-20T00:00:00Z&severity=high

Response: 200 OK
{
  "disparities": [
    {
      "id": "disp_12345",
      "timestamp": "2024-01-20T14:30:00Z",
      "endpoint": "/v1/presets",
      "type": "count_mismatch",
      "severity": "high",
      "details": {
        "field_path": "preset_count",
        "local_value": 5,
        "upstream_value": 4
      }
    }
  ],
  "summary": {
    "total": 15,
    "by_severity": {
      "high": 2,
      "medium": 8,
      "low": 5
    }
  }
}

File Format Specifications

Account Metadata (account.json)

{
  "version": "1.0",
  "id": "acc_12345",
  "name": "User Account",
  "email": "user@example.com",
  "created_at": "2024-01-20T10:00:00Z",
  "updated_at": "2024-01-20T15:30:00Z",
  "status": "active",
  "device_count": 2,
  "migration_info": {
    "started_at": "2024-01-18T09:00:00Z",
    "devices_migrated": 1,
    "devices_pending": 1,
    "mirror_active": true,
    "strategy": "gradual"
  },
  "bose_account_id": "bose-original-id",
  "data_sources": {
    "local": true,
    "bose_mirror": true,
    "primary": "bose"
  },
  "settings": {
    "auto_migration": false,
    "mirror_endpoints": ["/v1/presets", "/v1/recents"],
    "retention_days": 30
  }
}

Device Lifecycle (lifecycle.json)

{
  "version": "1.0",
  "device_id": "A81B6A536A98",
  "account_id": "acc_12345",
  "state": "active",
  "created_at": "2024-01-20T10:00:00Z",
  "updated_at": "2024-01-20T15:30:00Z",
  "state_history": [
    {
      "from": "unregistered",
      "to": "discovered",
      "timestamp": "2024-01-20T10:00:00Z",
      "reason": "mdns_discovery",
      "source": "discovery",
      "context": {
        "ip_address": "192.168.1.100",
        "discovery_method": "mdns"
      }
    },
    {
      "from": "discovered",
      "to": "active",
      "timestamp": "2024-01-20T10:05:00Z",
      "reason": "registration_complete",
      "source": "system"
    }
  ],
  "metadata": {
    "name": "Living Room Speaker",
    "type": "SoundTouch 30",
    "serial_number": "I6332527703739342000020",
    "firmware_version": "4.8.1.25341.2677643.1597353330",
    "mac_address": "A8:1B:6A:53:6A:98",
    "ip_address": "192.168.1.100",
    "last_seen": "2024-01-20T15:30:00Z",
    "is_legacy_id": false,
    "capabilities": ["multiroom", "bluetooth", "aux"]
  },
  "data_sources": {
    "presets": "local",
    "recents": "mirror_primary",
    "sources": "local"
  },
  "migration": {
    "from_bose_account": "bose-acc-xyz",
    "migrated_at": "2024-01-18T14:30:00Z",
    "method": "gradual",
    "rollback_available": true,
    "data_preserved": ["presets", "recents", "sources"]
  },
  "health": {
    "status": "healthy",
    "last_check": "2024-01-20T15:30:00Z",
    "response_time": 45,
    "connectivity": "online",
    "error_count": 0
  }
}

Event Log Format (events.log)

# SoundTouch Service Event Log - Device A81B6A536A98
# Format: TIMESTAMP|EVENT_ID|EVENT_TYPE|SOURCE|DATA_JSON
# Version: 1.0

2024-01-20T15:30:00.123Z|evt_12345|now_playing|websocket|{"source":"SPOTIFY","track":"Song Name","artist":"Artist Name","album":"Album Name"}
2024-01-20T15:30:30.456Z|evt_12346|volume_changed|websocket|{"volume":45,"muted":false,"previous_volume":40}
2024-01-20T15:31:00.789Z|evt_12347|preset_selected|websocket|{"preset":1,"source":"SPOTIFY","location":"spotify:track:123abc"}
2024-01-20T15:31:15.012Z|evt_12348|disparity_detected|mirror|{"endpoint":"/v1/presets","local_hash":"abc123","upstream_hash":"def456","severity":"medium"}
2024-01-20T15:32:00.345Z|evt_12349|health_check|system|{"response_time":42,"status":"healthy","connectivity":"online"}

Disparity Log Format (disparities.log)

# SoundTouch Service Disparity Log
# Format: TIMESTAMP|DISPARITY_ID|DEVICE_ID|ACCOUNT_ID|ENDPOINT|TYPE|SEVERITY|DETAILS_JSON
# Version: 1.0

2024-01-20T15:31:15.012Z|disp_12345|A81B6A536A98|acc_12345|/v1/presets|count_mismatch|medium|{"field_path":"preset_count","local_value":5,"upstream_value":4,"description":"Local has one additional preset"}
2024-01-20T15:32:45.678Z|disp_12346|A81B6A536A98|acc_12345|/v1/recents|timestamp_format|low|{"field_path":"recent[0].utc_time","local_value":"2024-01-20T15:30:00Z","upstream_value":"1705761000","description":"Timestamp format difference"}
2024-01-20T15:35:20.901Z|disp_12347|B92C7B647B09|acc_12345|/v1/account/full|structure_diff|high|{"field_path":"device[1].ip_address","local_value":"present","upstream_value":"missing","description":"IP address field missing in upstream response"}

State Machine Definitions

Device State Transitions

Unregistered → Discovered (via discovery)
Discovered → Registering (via user action/auto-registration)
Registering → Active (via successful registration)
Registering → Error (via registration failure)
Active → Migrating (via migration start)
Active → Offline (via connectivity loss)
Migrating → Active (via migration success)
Migrating → Error (via migration failure)
Offline → Active (via connectivity restored)
Error → Active (via error resolution)
Any State → Retired (via explicit retirement)

State Transition Rules

var StateTransitionRules = map[DeviceState][]DeviceState{
    DeviceStateUnregistered: {DeviceStateDiscovered},
    DeviceStateDiscovered:   {DeviceStateRegistering, DeviceStateOffline},
    DeviceStateRegistering:  {DeviceStateActive, DeviceStateError},
    DeviceStateActive:       {DeviceStateMigrating, DeviceStateOffline, DeviceStateRetired},
    DeviceStateMigrating:    {DeviceStateActive, DeviceStateError},
    DeviceStateOffline:      {DeviceStateActive, DeviceStateError, DeviceStateRetired},
    DeviceStateError:        {DeviceStateActive, DeviceStateOffline, DeviceStateRetired},
    DeviceStateRetired:      {}, // Terminal state
}

Transition Triggers

type TransitionTrigger struct {
    Event     DeviceEventType
    Condition func(*DeviceLifecycle, *DeviceEvent) bool
    Target    DeviceState
    Reason    string
}

var TransitionTriggers = []TransitionTrigger{
    {
        Event:     EventTypeDeviceOnline,
        Condition: isOfflineDevice,
        Target:    DeviceStateActive,
        Reason:    "connectivity_restored",
    },
    {
        Event:     EventTypeDeviceOffline,
        Condition: isActiveDevice,
        Target:    DeviceStateOffline,
        Reason:    "connectivity_lost",
    },
    {
        Event:     EventTypeMigrationStart,
        Condition: isActiveDevice,
        Target:    DeviceStateMigrating,
        Reason:    "migration_initiated",
    },
    // ... additional triggers
}

Event Processing

Event Queue Implementation

type EventQueue struct {
    buffer     chan DeviceEvent
    processors []EventProcessor
    storage    EventStorage
    config     EventQueueConfig
}

type EventQueueConfig struct {
    BufferSize      int           `json:"buffer_size"`
    ProcessorCount  int           `json:"processor_count"`
    FlushInterval   time.Duration `json:"flush_interval"`
    RetryAttempts   int           `json:"retry_attempts"`
    DeadLetterQueue bool          `json:"dead_letter_queue"`
}

type EventProcessor interface {
    ProcessEvent(event DeviceEvent) error
    CanHandle(eventType DeviceEventType) bool
}

Event Processing Flow

Event Input → Validation → Queue → Processing → Storage → Notification
     ↓            ↓          ↓         ↓          ↓          ↓
  Websocket    Schema    Buffer    Parallel   Files    Webhooks
  Discovery    Check     Memory    Workers    Logs     SSE
  API Call     Format              Retry      DB       Metrics
  System       Enrich              DLQ

Event Retention Policy

type RetentionPolicy struct {
    EventType     DeviceEventType `json:"event_type"`
    RetentionDays int            `json:"retention_days"`
    MaxCount      int            `json:"max_count"`
    Compression   bool           `json:"compression"`
}

var DefaultRetentionPolicies = []RetentionPolicy{
    {EventTypeNowPlaying, 7, 1000, true},
    {EventTypeVolumeChanged, 1, 100, false},
    {EventTypeDisparityFound, 30, 10000, true},
    {EventTypeMigrationStart, 365, -1, false}, // Keep forever
    {EventTypeHealthCheck, 7, 1000, true},
}

Development Requirements

KISS Principle (Keep It Simple, Stupid)

Quality Gates

Every change must pass these checks:

Testing Requirements

Simplicity-First Performance Approach

Aspect Simple Approach Optimization Only When Needed
Memory Usage Direct file operations, minimal caching Add caching if performance issues arise
CPU Usage Synchronous processing initially Add async processing if bottlenecks occur
Storage Simple append operations Add rotation/compression when files grow large
Networking Reuse existing patterns Optimize only if latency becomes problematic

Quality Assurance

Testing Strategy

Unit Testing

// Example test structure
func TestAccountManager_CreateAccount(t *testing.T) {
    tests := []struct {
        name    string
        input   CreateAccountRequest
        want    *Account
        wantErr bool
    }{
        {
            name: "valid account creation",
            input: CreateAccountRequest{Name: "Test Account"},
            want: &Account{Name: "Test Account", Status: "active"},
            wantErr: false,
        },
        {
            name: "empty name should fail",
            input: CreateAccountRequest{Name: ""},
            want: nil,
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := manager.CreateAccount(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("CreateAccount() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            // Additional assertions...
        })
    }
}

Integration Testing

Quality Gates

# Required before each commit
golangci-lint run --fix
go test ./...
go test -race ./...

# Required before milestone completion
go test ./... -v -cover
go test -bench=. ./...

Security Considerations

Simple Security Model

Input Validation

// Simple validation approach
func ValidateAccount(account *Account) error {
    if account.Name == "" {
        return errors.New("account name cannot be empty")
    }
    if len(account.Name) > 100 {
        return errors.New("account name too long")
    }
    if account.Email != "" && !isValidEmail(account.Email) {
        return errors.New("invalid email format")
    }
    return nil
}

Error Handling

Error Categories

type ErrorCategory string
const (
    ErrorCategoryValidation ErrorCategory = "validation"
    ErrorCategorySystem     ErrorCategory = "system"
    ErrorCategoryNetwork    ErrorCategory = "network"
    ErrorCategoryStorage    ErrorCategory = "storage"
    ErrorCategoryTimeout    ErrorCategory = "timeout"
    ErrorCategoryAuth       ErrorCategory = "authentication"
)

type ServiceError struct {
    Code        string                 `json:"code"`
    Message     string                 `json:"message"`
    Category    ErrorCategory          `json:"category"`
    Timestamp   time.Time              `json:"timestamp"`
    Context     map[string]interface{} `json:"context"`
    Retryable   bool                   `json:"retryable"`
    Severity    string                 `json:"severity"`
}

Error Recovery Strategies

  1. Transient Errors: Exponential backoff retry (3 attempts)
  2. Storage Errors: Graceful degradation with in-memory fallback
  3. Network Errors: Circuit breaker pattern with fallback data
  4. Validation Errors: Immediate response with detailed feedback
  5. System Errors: Alerting and automatic recovery attempts

Error Response Format

{
  "error": {
    "code": "DEVICE_NOT_FOUND",
    "message": "Device with ID 'A81B6A536A98' not found in account 'acc_12345'",
    "category": "validation",
    "timestamp": "2024-01-20T15:30:00Z",
    "context": {
      "account_id": "acc_12345",
      "device_id": "A81B6A536A98",
      "request_id": "req_67890"
    },
    "retryable": false,
    "severity": "error"
  },
  "request_id": "req_67890"
}

Monitoring and Observability

Simple Monitoring Approach

Basic Health Check

// Simple health check implementation
type HealthStatus struct {
    Status    string    `json:"status"`    // "healthy", "warning", "error"
    Timestamp time.Time `json:"timestamp"`
    Version   string    `json:"version"`
    Uptime    string    `json:"uptime"`
}

func (s *Server) HandleHealthCheck(w http.ResponseWriter, r *http.Request) {
    health := HealthStatus{
        Status:    "healthy",
        Timestamp: time.Now(),
        Version:   version,
        Uptime:    time.Since(startTime).String(),
    }
    
    // Simple checks
    if !s.canWriteToDataDir() {
        health.Status = "error"
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(health)
}

Leverage Existing Systems

Simple Metrics

// Basic counters - no complex metrics initially
type SimpleMetrics struct {
    AccountsCreated   int `json:"accounts_created"`
    DevicesActive     int `json:"devices_active"`
    EventsProcessed   int `json:"events_processed_today"`
    LastUpdate        time.Time `json:"last_update"`
}

// Update metrics in simple text file
func (m *SimpleMetrics) Save(dataDir string) error {
    data, err := json.MarshalIndent(m, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(filepath.Join(dataDir, "metrics.json"), data, 0644)
}

This technical specification provides comprehensive details for implementing the enhanced state management system while maintaining compatibility with existing SoundTouch service functionality and meeting the performance requirements for small hardware deployments.