618399f86d
- Replace single Load() with a watching Store that re-reads config files
(and config.local.yaml) and fans out a ReloadEvent with a per-field diff
so consumers can decide whether the change is hot-applicable or requires
a process restart.
- Wrap llm, retrieval, vector store, and object storage clients in
Reloadable* shells so the bootstrap can swap their underlying impls when
config changes without re-instantiating handlers.
- Make jwt.Manager and ops TokenIssuer mutable under a lock so secrets and
TTLs can be rotated live; thread default plan code through a setter on
the ops AdminUserService.
- Wire ConfigStore through bootstrap and every cmd/main.go, scheduler /
worker / tenant-api / ops-api start the watcher; services and handlers
take a config.Provider so they always read current values for things
like generation.stream_enabled, scheduler dispatch, retrieval, etc.
- Switch shared/config decoding off viper to a Kratos-derived runtime
package so env placeholders (\${VAR:default}) resolve consistently and
the same source machinery powers both the loader and the watcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
package config
|
|
|
|
import "reflect"
|
|
|
|
type FieldChange struct {
|
|
Path string
|
|
Hot bool
|
|
}
|
|
|
|
func Diff(previous, current *Config) []FieldChange {
|
|
if previous == nil || current == nil {
|
|
return nil
|
|
}
|
|
|
|
changes := make([]FieldChange, 0)
|
|
addChange := func(path string, hot bool) {
|
|
changes = append(changes, FieldChange{Path: path, Hot: hot})
|
|
}
|
|
|
|
if previous.Server != current.Server {
|
|
addChange("server", false)
|
|
}
|
|
if previous.Database != current.Database {
|
|
addChange("database", false)
|
|
}
|
|
if previous.MonitoringDatabase != current.MonitoringDatabase {
|
|
addChange("monitoring_database", false)
|
|
}
|
|
if previous.RabbitMQ != current.RabbitMQ {
|
|
addChange("rabbitmq", false)
|
|
}
|
|
if previous.Scheduler.HTTPHost != current.Scheduler.HTTPHost ||
|
|
previous.Scheduler.HTTPPort != current.Scheduler.HTTPPort {
|
|
addChange("scheduler.http", false)
|
|
}
|
|
if previous.Redis != current.Redis {
|
|
addChange("redis", false)
|
|
}
|
|
if previous.Cache != current.Cache {
|
|
addChange("cache", false)
|
|
}
|
|
if previous.Log != current.Log {
|
|
addChange("log", false)
|
|
}
|
|
|
|
if previous.Scheduler.InternalMetricsToken != current.Scheduler.InternalMetricsToken {
|
|
addChange("scheduler.internal_metrics_token", true)
|
|
}
|
|
if previous.Scheduler.DispatchInterval != current.Scheduler.DispatchInterval ||
|
|
previous.Scheduler.DispatchTimeout != current.Scheduler.DispatchTimeout ||
|
|
previous.Scheduler.DispatchBatchSize != current.Scheduler.DispatchBatchSize ||
|
|
previous.Scheduler.DispatchConcurrency != current.Scheduler.DispatchConcurrency ||
|
|
previous.Scheduler.GenerationQueueBackpressureLimit != current.Scheduler.GenerationQueueBackpressureLimit {
|
|
addChange("scheduler.dispatch", true)
|
|
}
|
|
if previous.MonitoringWorkers != current.MonitoringWorkers {
|
|
addChange("monitoring_workers", true)
|
|
}
|
|
if previous.MonitoringDispatch != current.MonitoringDispatch {
|
|
addChange("monitoring_dispatch", true)
|
|
}
|
|
if !reflect.DeepEqual(previous.Membership, current.Membership) {
|
|
addChange("membership", true)
|
|
}
|
|
if previous.BrandLibrary != current.BrandLibrary {
|
|
addChange("brand_library", true)
|
|
}
|
|
if previous.Qdrant != current.Qdrant {
|
|
addChange("qdrant", true)
|
|
}
|
|
if previous.ObjectStorage != current.ObjectStorage {
|
|
addChange("object_storage", true)
|
|
}
|
|
if previous.JWT != current.JWT {
|
|
addChange("jwt", true)
|
|
}
|
|
if previous.LLM != current.LLM {
|
|
addChange("llm", true)
|
|
}
|
|
if previous.Retrieval != current.Retrieval {
|
|
addChange("retrieval", true)
|
|
}
|
|
if previous.Generation.QueueSize != current.Generation.QueueSize ||
|
|
previous.Generation.WorkerConcurrency != current.Generation.WorkerConcurrency {
|
|
addChange("generation.worker", false)
|
|
}
|
|
if previous.Generation.StreamEnabled != current.Generation.StreamEnabled ||
|
|
previous.Generation.ArticleTimeout != current.Generation.ArticleTimeout {
|
|
addChange("generation", true)
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func RestartRequired(changes []FieldChange) bool {
|
|
for _, change := range changes {
|
|
if !change.Hot {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ChangePaths(changes []FieldChange, hot bool) []string {
|
|
paths := make([]string, 0, len(changes))
|
|
for _, change := range changes {
|
|
if change.Hot == hot {
|
|
paths = append(paths, change.Path)
|
|
}
|
|
}
|
|
return paths
|
|
}
|