package main import ( "time" ck "gitea.maximumdirect.net/eric/ConfigKit" ) // Config is experimental. It is not accepted by the production Audita CLI. // This prototype exercises the LLM boundary; the migration inventory covers // the remaining domain settings without duplicating their implementation here. type Config struct { Version int `yaml:"version"` ProfilesDir string `yaml:"profiles_dir"` Backends map[string]BackendConfig `yaml:"backends"` LLM LLMConfig `yaml:"llm"` } type BackendConfig struct { Endpoint string `yaml:"endpoint"` Concurrency int `yaml:"concurrency"` QueueCapacity int `yaml:"queue_capacity"` Credential *ck.Secret `yaml:"credential"` } type LLMConfig struct { ProposalProfile string `yaml:"proposal_profile"` ValidationProfile string `yaml:"validation_profile"` TransientRetries int `yaml:"transient_retries"` RepairAttempts int `yaml:"repair_attempts"` OperationTimeout time.Duration `yaml:"operation_timeout"` } func loadConfig(source ck.Source, overrides ...ck.Override) (*ck.Result[Config], error) { return ck.Load(source, ck.Options[Config]{ Overrides: overrides, Definition: ck.Definition[Config]{ Defaults: func() Config { return Config{ ProfilesDir: "profiles", LLM: LLMConfig{TransientRetries: 2, RepairAttempts: 2, OperationTimeout: 15 * time.Minute}, } }, Version: &ck.VersionRule{Path: ck.Keys("version"), Check: func(version ck.Version, v *ck.Validation) error { if !version.Present || version.Null || version.Text != "2" { v.Error(ck.Keys("version"), "version 2 is required") } return nil }}, Resolve: func(c *Config, r *ck.Resolution) error { ck.Default(r, ck.Keys("llm", "validation_profile"), &c.LLM.ValidationProfile, c.LLM.ProposalProfile, "validation uses proposal profile when omitted", ck.Keys("llm", "proposal_profile")) for id, backend := range c.Backends { // Map values are copied; record changes on the containing value. changed := false if !r.IsExplicit(ck.Keys("backends", id, "concurrency")) { backend.Concurrency = 1 changed = true } if !r.IsExplicit(ck.Keys("backends", id, "queue_capacity")) { backend.QueueCapacity = 1024 changed = true } if changed { c.Backends[id] = backend r.Record(ck.Keys("backends", id), ck.ChangeDerivedDefault, "finite backend defaults") } } return nil }, Validate: func(c *Config, v *ck.Validation) { if c.ProfilesDir == "" { v.Error(ck.Keys("profiles_dir"), "profile directory is required") } if c.LLM.ProposalProfile == "" { v.Error(ck.Keys("llm", "proposal_profile"), "proposal profile is required") } if c.LLM.ValidationProfile == "" { v.Error(ck.Keys("llm", "validation_profile"), "validation profile is required") } if c.LLM.TransientRetries < 0 { v.Error(ck.Keys("llm", "transient_retries"), "retry budget must be non-negative") } if c.LLM.RepairAttempts < 0 || c.LLM.RepairAttempts > 3 { v.Error(ck.Keys("llm", "repair_attempts"), "PromptKit supports zero through three repairs") } if c.LLM.OperationTimeout <= 0 { v.Error(ck.Keys("llm", "operation_timeout"), "operation timeout must be positive") } if len(c.Backends) == 0 { v.Error(ck.Keys("backends"), "at least one backend is required") } for id, b := range c.Backends { if b.Concurrency < 1 { v.Error(ck.Keys("backends", id, "concurrency"), "backend concurrency must be positive") } if b.QueueCapacity < 0 { v.Error(ck.Keys("backends", id, "queue_capacity"), "queue capacity must be non-negative") } if b.Endpoint == "" { v.Error(ck.Keys("backends", id, "endpoint"), "backend endpoint is required") } } }, }, }) }