package catalog import "sync/atomic" // Live holds the catalog that is currently in force. // // The catalog was built once, from YAML, at process start, which made adding a // model an operator procedure with downtime: edit a file on the control plane, // restart it, wait out the settling window, watch replicas churn while adoption // catches up. That is a reasonable way to manage a fixed set of production // models and a terrible one for a platform whose whole promise is that people // can try things. // // A Catalog stays immutable — validation happens once, in New, and a holder can // read one without a lock or a copy. What changes is which immutable catalog is // current. Readers take the pointer and work from it for the rest of their // operation, so a swap mid-request is invisible rather than a race: the request // finishes against the catalog it started with, which is the behaviour anyone // would want anyway. type Live struct { v atomic.Pointer[Catalog] } // NewLive holds an initial catalog. func NewLive(c *Catalog) *Live { l := &Live{} l.v.Store(c) return l } // Load returns the catalog in force. Never nil once constructed. func (l *Live) Load() *Catalog { return l.v.Load() } // Store replaces it. The caller has already validated the new one, because // New is the only way to build a Catalog and it refuses an invalid one. func (l *Live) Store(c *Catalog) { l.v.Store(c) }