// 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/secrets" "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 } // ManagerOf is the node that holds a licence's refresh token readably, empty if none is named. // // Empty for every static-key licence, which has nothing to refresh, and for a refreshable-grant one // before its manager is set (novox/hq ADR 0050). func (l *Licences) ManagerOf(ctx context.Context, licence string) (string, error) { var manager *string err := l.store.Pool().QueryRow(ctx, `select manager from licence where name = $1`, licence).Scan(&manager) if errors.Is(err, pgx.ErrNoRows) { return "", fmt.Errorf("this mesh has no licence called %q", licence) } if err != nil { return "", err } if manager == nil { return "", nil } return *manager, nil } // SetManager names the one node that holds a licence's refresh token and refreshes it centrally. // // **Only a refreshable-grant licence has one.** A static-key licence has no refresh token, so naming // a manager for it is refused rather than kept — the absent manager is part of what keeps a static // key from ever growing a value something holds readably at rest (novox/hq ADR 0050). The bound // "the manager node only" starts here, at the one place a manager is written. func (l *Licences) SetManager(ctx context.Context, licence, node string) error { if strings.TrimSpace(node) == "" { return errors.New("a manager needs a node") } vendor, err := l.vendorOf(ctx, licence) if err != nil { return err } adapter, err := adapters.For(vendor) if err != nil { return err } if adapter.Shape() != adapters.RefreshableGrant { return fmt.Errorf( "%q is a %s licence; only a refreshable-grant licence has a manager, because only it "+ "has a refresh token to hold", licence, adapter.Shape()) } tag, err := l.store.Pool().Exec(ctx, `update licence set manager = $2 where name = $1`, licence, node) if err != nil { return err } if tag.RowsAffected() == 0 { return fmt.Errorf("this mesh has no licence called %q", licence) } return nil } // SetRefreshGrant stores, or replaces, a licence's refresh token as its at-rest envelope. // // **The envelope is opaque here.** It was produced by the manager node — the only place the refresh // token is ever in the clear (novox/hq ADR 0050, Phase C) — and this context keeps it and forwards // it to a refresh without opening it. The control plane holds no key that could, which is the whole // point of where the carve-out draws the line. func (l *Licences) SetRefreshGrant(ctx context.Context, licence string, at secrets.AtRest) error { if at.Token == "" || at.WrappedKey == "" || at.ManagerKey == "" { return errors.New("an incomplete refresh-token envelope is not one to keep") } _, err := l.store.Pool().Exec(ctx, `insert into refresh_grant (licence, token, wrapped_key, manager_key) values ($1, $2, $3, $4) on conflict (licence) do update set token = excluded.token, wrapped_key = excluded.wrapped_key, manager_key = excluded.manager_key, updated_at = now()`, licence, at.Token, at.WrappedKey, at.ManagerKey) if err != nil && strings.Contains(err.Error(), "refresh_grant_licence_fkey") { return fmt.Errorf("this mesh has no licence called %q", licence) } return err } // RefreshGrant is a licence's refresh token as its at-rest envelope, and whether one is stored. func (l *Licences) RefreshGrant(ctx context.Context, licence string) (secrets.AtRest, bool, error) { var at secrets.AtRest err := l.store.Pool().QueryRow(ctx, `select token, wrapped_key, manager_key from refresh_grant where licence = $1`, licence). Scan(&at.Token, &at.WrappedKey, &at.ManagerKey) if errors.Is(err, pgx.ErrNoRows) { return secrets.AtRest{}, false, nil } if err != nil { return secrets.AtRest{}, false, err } return at, true, nil } // Refresh mints a new access token for a refreshable-grant licence, seals it to every holder, and // leaves the refresh token where it is — re-encrypted at rest if the vendor rotated it too. // // **The lease.** One refresh of a licence at a time, held as a transaction-scoped advisory lock on // the licence: two refreshes serialise rather than both minting a token and racing to publish. This // is doc 13's single-actor rotation lease, expressed against the database that is the source of // truth (novox/hq ADR 0003) rather than reinvented. // // **It reuses rotation's reseal-and-publish, not its value source.** doc 13's rotate discards a // mesh-minted secret and regenerates it; here the new access token comes from the vendor refresh // instead, and is then sealed per holder (secrets.Seal, exactly as Accept does) and delivered on the // next push — the same publish path any credential change takes. The refresh token is never sealed // to a holder, so `KeyFor` cannot deliver it. // // **All or nothing.** The reseal, the grant replacement and the lease are one transaction: a refresh // that cannot finish leaves every holder on the token it had and the stored grant untouched — a // licence that has not refreshed, which is far better than one half refreshed (doc 13). // // The vendor refresh itself is the injected VendorRefresher (novox/hq ADR 0050, Phase C); with none // plugged in, the adapter refuses here and nothing is changed. func (l *Licences) Refresh(ctx context.Context, licence string, keys SealingKeys) (int, error) { tx, err := l.store.Pool().Begin(ctx) if err != nil { return 0, err } defer func() { _ = tx.Rollback(context.WithoutCancel(ctx)) }() // The lease. Released when the transaction ends, either way. if _, err := tx.Exec(ctx, `select pg_advisory_xact_lock(hashtext($1)::bigint)`, licence); err != nil { return 0, fmt.Errorf("cannot take the refresh lease on %q: %w", licence, err) } var vendor string var manager *string err = tx.QueryRow(ctx, `select vendor, manager from licence where name = $1`, licence).Scan(&vendor, &manager) if errors.Is(err, pgx.ErrNoRows) { return 0, fmt.Errorf("this mesh has no licence called %q", licence) } if err != nil { return 0, err } adapter, err := adapters.For(vendor) if err != nil { return 0, err } refresher, ok := adapter.(adapters.Refresher) if !ok { // The type assertion is what gates the carve-out to refreshable-grant vendors: a static key // is not a Refresher, so it can never reach the machinery that holds a token readably. return 0, fmt.Errorf( "%q is a %s licence and cannot be refreshed; only a refreshable-grant licence has a "+ "refresh token", licence, adapter.Shape()) } if manager == nil || *manager == "" { return 0, fmt.Errorf( "%q has no manager named, so there is no node to refresh it. Name one:\n"+ " licence manager %s ", licence, licence) } var at secrets.AtRest err = tx.QueryRow(ctx, `select token, wrapped_key, manager_key from refresh_grant where licence = $1`, licence). Scan(&at.Token, &at.WrappedKey, &at.ManagerKey) if errors.Is(err, pgx.ErrNoRows) { return 0, fmt.Errorf( "%q has no refresh token stored yet; its manager %s adopts one first "+ "(novox/hq ADR 0050, Phase C)", licence, *manager) } if err != nil { return 0, err } result, err := refresher.Refresh(ctx, adapters.RefreshInput{Licence: licence, Manager: *manager, AtRest: at}) if err != nil { return 0, err } if strings.TrimSpace(result.AccessToken) == "" { return 0, fmt.Errorf( "the refresh produced no access token for %q, so nothing was resealed", licence) } // Reseal the new access token to the holders that exist now — the same set Accept seals to — and // keep no readable copy. holders, err := holdersTx(ctx, tx, licence) if err != nil { return 0, err } sealed := 0 for _, h := range holders { key, err := keys(h.Node) if err != nil { return 0, err } if key == "" { return 0, fmt.Errorf( "%s has no sealing key, so the new access token cannot be sealed to it", h.Node) } blob, err := secrets.Seal(key, []byte(result.AccessToken)) if err != nil { return 0, err } if _, err := tx.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, blob, key); err != nil { return 0, err } sealed++ } // The refresh token stays put unless the vendor rotated it, in which case the refresher returned // it already re-encrypted at rest — replaced here without ever being seen in the clear. if result.NewAtRest != nil { if result.NewAtRest.Token == "" || result.NewAtRest.WrappedKey == "" || result.NewAtRest.ManagerKey == "" { return 0, fmt.Errorf( "the refresh returned an incomplete re-sealed refresh token for %q", licence) } if _, err := tx.Exec(ctx, `update refresh_grant set token = $2, wrapped_key = $3, manager_key = $4, updated_at = now() where licence = $1`, licence, result.NewAtRest.Token, result.NewAtRest.WrappedKey, result.NewAtRest.ManagerKey); err != nil { return 0, err } } if err := tx.Commit(ctx); err != nil { return 0, err } return sealed, nil } // holdersTx reads a licence's holders inside a transaction, so the reseal set is consistent under // the refresh lease. func holdersTx(ctx context.Context, tx pgx.Tx, licence string) ([]Holder, error) { rows, err := tx.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() } // 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 }