// Package licences is the context that holds which model access exists and who may use it. // // novox/hq ADR 0024. It is the first provision answered by a **record rather than a node**: a // hosted model is on nobody's machine, is reached over the public internet, and the rule that // refuses two ends sharing no private network must not apply to it. // // It owns its store exclusively (novox/hq ADR 0008): a database called `licences`, reached with a // credential no other context holds — including `inventory`, in the same process. It refers to // nodes by name, which is what crossing a context boundary is allowed to carry. package licences import ( "context" "embed" "encoding/json" "errors" "fmt" "sort" "strings" "time" "github.com/jackc/pgx/v5" "github.com/novox/mesh-control/internal/licences/adapters" "github.com/novox/mesh-control/internal/store" ) // Name is what this context is called: its database and its credential are named after it. const Name = "licences" // Provision is what a module requires in order to be given one. // // One name for all of them, because *which* licence is the operator's choice per consumer rather // than something a module asks for — a module that required `anthropic` by name could never be // moved onto a mesh-hosted model without editing it. const Provision = "model-access" //go:embed migrations/*.sql var files embed.FS // Migrations are this context's schema changes, in order. func Migrations() ([]store.Migration, error) { return store.LoadMigrations(files, "migrations") } // Licences is this context, holding the store it exclusively owns. type Licences struct{ store *store.Store } // Open connects to the licence store. func Open(ctx context.Context) (*Licences, error) { s, err := store.Open(ctx, Name) if err != nil { return nil, err } return &Licences{store: s}, nil } func (l *Licences) Close() { l.store.Close() } // Ready waits for the database to answer. func (l *Licences) Ready(ctx context.Context, within time.Duration) error { return l.store.Ready(ctx, within) } // A Licence is one way to reach a model, under the name a person calls it. type Licence struct { Name string // Vendor is which company sells this licence, and selects the adapter that runs its lifecycle // (novox/hq ADR 0050). Named `vendor`, not `provider`: the inventory already uses "provider" // for *which node answers a brokered provision*, and one word must not carry two unrelated // facts. Vendor string Serves map[string]any Added time.Time } // A Holder is one consumer using a licence, and whether it has been given the key. type Holder struct { Licence string Node string Module string // Sealed is empty when no key has been supplied since this holder was recorded. Sealed string } // Add records a licence under the operator's own name for it. func (l *Licences) Add(ctx context.Context, name, vendor string, serves map[string]any) error { if strings.TrimSpace(name) == "" || strings.TrimSpace(vendor) == "" { return errors.New("a licence needs a name and a vendor") } if serves == nil { serves = map[string]any{} } body, err := json.Marshal(serves) if err != nil { return err } _, err = l.store.Pool().Exec(ctx, `insert into licence (name, vendor, serves) values ($1, $2, $3) on conflict (name) do update set vendor = excluded.vendor, serves = excluded.serves`, name, vendor, body) return err } // All is every licence this mesh knows about. func (l *Licences) All(ctx context.Context) ([]Licence, error) { rows, err := l.store.Pool().Query(ctx, `select name, vendor, serves, added_at from licence order by name`) if err != nil { return nil, err } defer rows.Close() var out []Licence for rows.Next() { var one Licence var body []byte if err := rows.Scan(&one.Name, &one.Vendor, &body, &one.Added); err != nil { return nil, err } if err := json.Unmarshal(body, &one.Serves); err != nil { return nil, err } out = append(out, one) } return out, rows.Err() } // Forget removes a licence, and with it every record of who held it. // // **A licence outliving its holder is a live credential nobody is watching** (ADR 0024). This is // the other direction and has the same shape: what the mesh no longer grants, it stops naming. // The key itself is not the mesh's to revoke — that is done where the licence was bought, and // saying so is more use than pretending otherwise. func (l *Licences) Forget(ctx context.Context, name string) error { tag, err := l.store.Pool().Exec(ctx, `delete from licence where name = $1`, name) if err != nil { return err } if tag.RowsAffected() == 0 { return fmt.Errorf("this mesh has no licence called %q", name) } return nil } // Use records that a consumer holds a licence. // // Recorded before any key exists, deliberately. Who uses what is a decision; the key is a value // somebody supplies afterwards, and often by a different person. func (l *Licences) Use(ctx context.Context, licence, node, module string) error { _, err := l.store.Pool().Exec(ctx, `insert into licence_holder (licence, node, module) values ($1, $2, $3) on conflict (licence, node, module) do nothing`, licence, node, module) if err != nil && strings.Contains(err.Error(), "licence_holder_licence_fkey") { return fmt.Errorf("this mesh has no licence called %q", licence) } return err } // StopUsing takes a consumer off a licence, and its sealed key with it. func (l *Licences) StopUsing(ctx context.Context, licence, node, module string) error { _, err := l.store.Pool().Exec(ctx, `delete from licence_holder where licence = $1 and node = $2 and module = $3`, licence, node, module) return err } // HoldersOf is every consumer using a licence. func (l *Licences) HoldersOf(ctx context.Context, licence string) ([]Holder, error) { rows, err := l.store.Pool().Query(ctx, `select licence, node, module, coalesce(sealed, '') from licence_holder where licence = $1 order by node, module`, licence) if err != nil { return nil, err } defer rows.Close() var out []Holder for rows.Next() { var h Holder if err := rows.Scan(&h.Licence, &h.Node, &h.Module, &h.Sealed); err != nil { return nil, err } out = append(out, h) } return out, rows.Err() } // Chosen is the licence a consumer was put on, empty if it was put on none. func (l *Licences) Chosen(ctx context.Context, node, module string) (string, error) { var name string err := l.store.Pool().QueryRow(ctx, `select licence from licence_holder where node = $1 and module = $2`, node, module). Scan(&name) if errors.Is(err, pgx.ErrNoRows) { return "", nil } return name, err } // KeyFor is the sealed key for one holder, empty if none has been supplied since it was recorded. // // What is stored is what is delivered, routed through the vendor's adapter so a refreshable-grant // vendor can strip its refresh token here in Phase B (novox/hq ADR 0050). For a static-key vendor // that step is the identity — the sealed blob is what the holder receives — so this is unchanged // for today's vendors. An unregistered vendor is not consulted: a static-key blob delivers as it is, // and a licence whose key was accepted at all necessarily had a registered adapter. func (l *Licences) KeyFor(ctx context.Context, licence, node, module string) (string, error) { var sealed *string err := l.store.Pool().QueryRow(ctx, `select sealed from licence_holder where licence = $1 and node = $2 and module = $3`, licence, node, module).Scan(&sealed) if errors.Is(err, pgx.ErrNoRows) || sealed == nil { return "", nil } if err != nil { return "", err } if adapter, err := l.adapterFor(ctx, licence); err == nil { return adapter.Deliver(*sealed), nil } return *sealed, nil } // vendorOf reads a licence's vendor, the field that selects its adapter. func (l *Licences) vendorOf(ctx context.Context, licence string) (string, error) { var vendor string err := l.store.Pool().QueryRow(ctx, `select vendor from licence where name = $1`, licence).Scan(&vendor) if errors.Is(err, pgx.ErrNoRows) { return "", fmt.Errorf("this mesh has no licence called %q", licence) } return vendor, err } // adapterFor is the adapter a licence's vendor selects (novox/hq ADR 0050). func (l *Licences) adapterFor(ctx context.Context, licence string) (adapters.Adapter, error) { vendor, err := l.vendorOf(ctx, licence) if err != nil { return nil, err } return adapters.For(vendor) } // SealingKeys is what Accept needs: each holder's node and the key to seal to it. type SealingKeys func(node string) (string, error) // Accept takes a key somebody supplied, seals it to every holder, and discards the plaintext. // // **The missing verb** (ADR 0024). Every credential the mesh handles otherwise it generated // itself; an API key arrives from a person, and a mesh that kept operator-supplied keys readably // is the arrangement this project measured and rejected. // // **It seals to the holders that exist now.** A holder recorded afterwards has no key, and the // mesh cannot make one — it discarded the only copy. That is reported rather than hidden: the // remedy is to supply the key again, which is a thing a person can do, and delivering nothing // while reporting success is not. func (l *Licences) Accept(ctx context.Context, licence, value string, keys SealingKeys) (int, error) { if strings.TrimSpace(value) == "" { return 0, errors.New("an empty key is not a key") } // The vendor selects the adapter that seals it (novox/hq ADR 0050). A static-key vendor's accept // is the generic seal; the dispatch is what lets a refreshable-grant vendor do otherwise in // Phase B without this layer changing. An unknown vendor is refused here, before any key is // touched. adapter, err := l.adapterFor(ctx, licence) if err != nil { return 0, err } holders, err := l.HoldersOf(ctx, licence) if err != nil { return 0, err } if len(holders) == 0 { // Refused rather than stored for later, because storing it for later means storing it // readably — which is the whole thing this refuses to do. return 0, fmt.Errorf( "nothing uses %q yet, and the mesh does not keep a key it cannot seal to somebody. "+ "Put a consumer on it first, then supply the key", licence) } sealed := 0 for _, h := range holders { key, err := keys(h.Node) if err != nil { return sealed, err } if key == "" { return sealed, fmt.Errorf( "%s has no sealing key, so nothing can be sealed to it — it joins again to get one", h.Node) } made, err := adapter.Accept(value, key, key) if err != nil { return sealed, err } if _, err := l.store.Pool().Exec(ctx, `update licence_holder set sealed = $4, node_key = $5 where licence = $1 and node = $2 and module = $3`, h.Licence, h.Node, h.Module, made.ForConsumer, key); err != nil { return sealed, err } sealed++ } return sealed, nil } // Names is every licence's name, sorted — what a refusal lists when a consumer has not chosen. func Names(all []Licence) []string { out := make([]string, 0, len(all)) for _, one := range all { out = append(out, one.Name) } sort.Strings(out) return out }