package inventory import ( "context" "github.com/novox/mesh-controller/internal/catalogue" ) // The seats the mesh has, as data (novox/hq ADR 0122). // // The set the control plane reads is a table here, not a slice compiled into it. It is seeded from // the binary's defaults the first time the mesh comes up (SeedSeats), and thereafter it is the live // copy: a rename or an added seat is a write here, and the control plane loads it at startup rather // than being rebuilt for it. // Seats is every seat the mesh defines, read from the store. func (i *Inventory) Seats(ctx context.Context) ([]catalogue.Seat, error) { rows, err := i.store.Pool().Query(ctx, `select name, scope, delivers, decided from seat order by name`) if err != nil { return nil, err } defer rows.Close() var seats []catalogue.Seat for rows.Next() { var s catalogue.Seat if err := rows.Scan(&s.Name, &s.Scope, &s.Delivers, &s.Decision); err != nil { return nil, err } seats = append(seats, s) } return seats, rows.Err() } // SeedSeats writes the mesh's default set into the table where it is not already present. // // **Idempotent, and never overwriting.** Run every time the control plane migrates, it fills an // empty table on first boot and adds a seat a new release ships — but it leaves a row already there // exactly as it is, so an operator's rename in the table is not undone by the next deploy putting // the old name back. What a release removes from the defaults is not deleted here either; retiring a // seat is its own decision, not a silent consequence of it dropping out of the binary. func (i *Inventory) SeedSeats(ctx context.Context, defaults []catalogue.Seat) (int, error) { var added int for _, s := range defaults { tag, err := i.store.Pool().Exec(ctx, `insert into seat (name, scope, delivers, decided) values ($1, $2, $3, $4) on conflict (name) do nothing`, s.Name, s.Scope, s.Delivers, s.Decision) if err != nil { return added, err } added += int(tag.RowsAffected()) } return added, nil }