Files
dance-lessons-coach/adr/0023-config-hot-reloading.md
Gabriel Radureau a24b4fdb3b 📝 docs(adr): homogenize 23 ADRs + rewrite README (Tâche 7 migration) (#18)
## Summary

Homogenize all 23 ADRs to a single canonical header format, and rewrite `adr/README.md` to match the actual state of the corpus.

This is **Tâche 7** of the ARCODANGE Phase 1 migration (Claude Code → Mistral Vibe). Independent from PR #17 (Tâche 6 — restructure AGENTS.md) — both can merge in any order. No code changes; only documentation.

## Changes

### 1. Homogenize 21 ADR headers (commit `db09d0a`)

The audit (Tâche 6 Phase A, Mistral intent-router agent, 2026-05-02) had identified **3 inconsistent header formats** :

- **F1** — list bullets (`* Status:` / `* Date:` / `* Deciders:`) : 11 ADRs (0001-0008, 0011, 0014, 0023)
- **F2** — bold fields (`**Status:**` / `**Date:**` / `**Authors:**`) : 9 ADRs (0009, 0010, 0012, 0013, 0015, 0016, 0017, 0018, 0019)
- **F3** — dedicated section (`## Status\n**Value** `) : 5 ADRs (0020, 0021, 0022, 0024, 0025)

Plus mixed metadata names (Authors / Deciders / Decision Date / Implementation Date / Implementation Status / Last Updated) and decorative emojis on status values made the corpus hard to scan or template against.

**Canonical format adopted** (see `adr/README.md` for full template) :

```markdown
# NN. Title

**Status:** <Proposed | Accepted | Implemented | Partially Implemented | Approved | Rejected | Deferred | Deprecated | Superseded by ADR-NNNN>
**Date:** YYYY-MM-DD
**Authors:** Name(s)

[optional **Field:** ... lines]

## Context...
```

**Transformations applied** (via `/tmp/homogenize-adrs.py` script, 23 files scanned, 21 modified — 0010 and 0012 were already conform) :

- F1 list bullets → bold fields
- F2 cleanup : `**Deciders:**` → `**Authors:**`, strip status emojis
- F3 sections : `## Status\n**Value** ` → `**Status:** Value` (single line)
- Strip decorative emojis from `**Status:**` and `**Implementation Status:**`
- Convert `* Last Updated:` / `* Implementation Status:` / `* Decision Drivers:` / `* Decision Date:` to bold
- Date typo fix : `2024-04-XX` → `2026-04-XX` for ADRs 0018, 0019 (off-by-2-years in original)
- Normalize multiple blank lines after header (max 1)

**ADR body content is preserved unchanged.** Only headers transformed.

### 2. Rewrite `adr/README.md` (commit `d64ab02`)

Previous README had multiple inconsistencies :

- Index table listed wrong titles for ADRs 0010-0021 (looked like an aspirational forecast that never matched reality — e.g. "0011 = Trunk-Based Development" but real 0011 is absent and Trunk-Based Development is actually 0017)
- Listed entries for ADRs 0011 (validation library) and 0014 (gRPC) but **these files do not exist** in the repo
- 0024 (BDD Test Organization) was missing from the detail list
- Template still showed the obsolete F1 format (`* Status:`)
- Decorative emojis on every status entry

Rewrite :

- Index table **regenerated from actual file contents** (title from H1, status from `**Status:**` line) — emoji-free, accurate
- Notes that 0011 / 0014 are not currently in use (reserved)
- Updated template block matches the canonical format
- Status Legend extended with `Approved`, `Partially Implemented`, `Deferred`
- Added note that 0026 is the next free number for new ADRs

## Test plan

- [x] All 23 ADRs follow `**Status:**` / `**Date:**` / `**Authors:**` (verified via grep)
- [x] No more occurrences of `* Status:` (F1) or `## Status` (F3) in any ADR header
- [x] No more emojis on `**Status:**` lines
- [x] `adr/README.md` index links resolve to existing files (no more 0011 / 0014 dead links)
- [x] Pre-commit hooks pass (`go mod tidy`, `go fmt`, `swag fmt`)

## Migration context

Part of Phase 1 of the ARCODANGE migration from Claude Code to Mistral Vibe. Tâche 7 of the curriculum.

Independent from PR #17 (which restructures `AGENTS.md`). The two PRs touch disjoint files — no merge conflict expected when both are merged.

🤖 Generated with [Claude Code](https://claude.com/claude-code) (Opus 4.7, 1M context). Mistral Vibe (intent-router agent / mistral-medium-3.5) did the original audit identifying the 3 formats during Tâche 6 Phase A.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Mistral Vibe (devstral-2 / mistral-medium-3.5)
Reviewed-on: #18
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-05-03 11:01:13 +02:00

8.7 KiB

Config Hot Reloading Strategy

Status: Proposed Authors: Gabriel Radureau, AI Agent Date: 2026-04-05

Context and Problem Statement

The dance-lessons-coach application currently loads configuration once at startup using Viper, which supports file-based configuration, environment variables, and defaults. However, the current implementation does not support runtime configuration changes without restarting the application.

We need to determine whether and how to implement config hot reloading - the ability to detect changes to the optional config.yaml file and apply those changes without requiring a full application restart.

Decision Drivers

  • Development convenience: Hot reloading would allow developers to change configuration without restarting the server during development
  • Production flexibility: Ability to adjust certain configuration parameters without downtime
  • Complexity: Hot reloading adds significant complexity to the codebase
  • Safety: Some configuration changes require careful handling to avoid runtime errors
  • Viper capabilities: Viper already supports file watching through viper.WatchConfig()
  • Configuration scope: Not all configuration parameters can or should be hot-reloaded

Considered Options

Option 1: Full Hot Reloading with Viper WatchConfig

Implement comprehensive hot reloading using Viper's built-in WatchConfig() functionality to monitor the config file and automatically reload when changes are detected.

Option 2: Selective Hot Reloading

Only allow hot reloading for specific configuration sections that are safe to change at runtime (e.g., logging level, feature flags) while requiring restart for others (e.g., server host/port, database credentials).

Option 3: Manual Reload Endpoint

Add an admin endpoint (e.g., POST /api/admin/reload-config) that triggers configuration reload when called, giving explicit control over when reloading happens.

Option 4: No Hot Reloading

Maintain the current approach of loading configuration only at startup, requiring application restart for any configuration changes.

Decision Outcome

Chosen option: "Selective Hot Reloading" because it provides the benefits of runtime configuration changes while maintaining safety and control. This approach:

  • Allows safe configuration changes without restart
  • Prevents dangerous runtime changes to critical parameters
  • Leverages Viper's existing capabilities
  • Provides a clear boundary between hot-reloadable and non-hot-reloadable settings

Implementation Strategy

Hot-Reloadable Configuration

The following configuration parameters will support hot reloading:

  • Logging level (logging.level)
  • Feature flags (api.v2_enabled)
  • Telemetry sampling (telemetry.sampler.type, telemetry.sampler.ratio)
  • JWT TTL (auth.jwt.ttl)

Non-Hot-Reloadable Configuration

These parameters will require application restart:

  • Server settings (server.host, server.port)
  • Database credentials (database.*)
  • JWT secret (auth.jwt_secret)
  • Admin credentials (auth.admin_master_password)

Implementation Plan

// Add to config package
type ConfigManager struct {
    config     *Config
    viper      *viper.Viper
    changeChan chan struct{}
    stopChan   chan struct{}
}

func NewConfigManager() (*ConfigManager, error) {
    // Initialize Viper and load initial config
    // Start file watcher if config file exists
}

func (cm *ConfigManager) StartWatching() {
    if cm.viper != nil {
        cm.viper.WatchConfig()
        cm.viper.OnConfigChange(func(e fsnotify.Event) {
            cm.handleConfigChange()
        })
    }
}

func (cm *ConfigManager) handleConfigChange() {
    // Reload only safe configuration sections
    // Update logging level if changed
    // Update feature flags if changed
    // Notify other components of changes
    
    log.Info().Msg("Configuration reloaded (partial)")
}

// Safe getter methods that work with hot reloading
func (cm *ConfigManager) GetLogLevel() string {
    // Return current value, potentially updated via hot reload
}

Configuration File Monitoring

// In main application setup
func main() {
    configManager, err := config.NewConfigManager()
    if err != nil {
        log.Fatal().Err(err).Msg("Failed to initialize config")
    }
    
    // Start watching for config changes
    configManager.StartWatching()
    
    // Use configManager throughout application instead of direct config access
}

Pros and Cons of the Options

Option 1: Full Hot Reloading with Viper WatchConfig

  • Good: Maximum flexibility for configuration changes
  • Good: Leverages Viper's built-in capabilities
  • Good: Good for development workflow
  • Bad: High risk of runtime errors from unsafe changes
  • Bad: Complex to implement safely
  • Bad: Hard to debug configuration-related issues

Option 2: Selective Hot Reloading (Chosen)

  • Good: Safe approach with clear boundaries
  • Good: Balances flexibility and stability
  • Good: Easier to implement and maintain
  • Good: Clear documentation of what can be changed
  • Bad: More complex than no hot reloading
  • Bad: Requires careful design of config access patterns

Option 3: Manual Reload Endpoint

  • Good: Explicit control over when reloading happens
  • Good: Can be secured with authentication
  • Good: Good for production environments
  • Bad: Less convenient for development
  • Bad: Requires additional API endpoint management
  • Bad: Still needs same safety considerations as automatic reloading

Option 4: No Hot Reloading

  • Good: Simplest approach
  • Good: No risk of runtime configuration errors
  • Good: Easier to reason about application state
  • Bad: Requires restart for any configuration change
  • Bad: Less flexible for production adjustments
  • Bad: Slower development iteration

Configuration Change Handling

Safe Change Pattern

// Example: Logging level change
func (cm *ConfigManager) handleConfigChange() {
    // Get new config values
    newConfig := &Config{}
    if err := cm.viper.Unmarshal(newConfig); err != nil {
        log.Error().Err(err).Msg("Failed to unmarshal new config")
        return
    }
    
    // Apply safe changes
    if newConfig.Logging.Level != cm.config.Logging.Level {
        if err := cm.applyLogLevelChange(newConfig.Logging.Level); err != nil {
            log.Error().Err(err).Msg("Failed to apply log level change")
        }
    }
    
    // Update other safe parameters...
}

func (cm *ConfigManager) applyLogLevelChange(newLevel string) error {
    // Validate new level
    level := parseLogLevel(newLevel)
    
    // Apply change
    zerolog.SetGlobalLevel(level)
    cm.config.Logging.Level = newLevel
    
    log.Info().Str("new_level", newLevel).Msg("Log level updated")
    return nil
}

Error Handling

  • Invalid configuration changes are logged but don't crash the application
  • Failed changes revert to previous known-good values
  • Critical errors during reload trigger application shutdown
  • All changes are logged for audit purposes

Configuration File Example with Hot-Reloadable Settings

# config.yaml - These settings can be hot-reloaded
server:
  host: "0.0.0.0"
  port: 8080

logging:
  level: "info"  # Can be changed without restart
  json: false
  output: ""

api:
  v2_enabled: false  # Can be changed without restart

telemetry:
  enabled: false
  sampler:
    type: "parentbased_always_on"  # Can be changed without restart
    ratio: 1.0

Migration Plan

  1. Phase 1: Implement ConfigManager wrapper around existing config
  2. Phase 2: Add selective hot reloading for logging level
  3. Phase 3: Extend to feature flags and telemetry settings
  4. Phase 4: Add documentation and examples
  5. Phase 5: Update all components to use ConfigManager instead of direct config access

Monitoring and Observability

  • Log all configuration changes with timestamps
  • Include previous and new values in change logs
  • Add metrics for configuration reload events
  • Provide admin endpoint to view current configuration

Security Considerations

  • Config file permissions should be restrictive
  • Hot reloading should be disabled in production by default
  • Configuration changes should be audited
  • Sensitive parameters should never be hot-reloadable

Future Enhancements

  • Configuration change webhooks
  • Configuration versioning and rollback
  • Configuration validation before applying changes
  • Multi-file configuration support