matrixcloud / internal /store /cloud.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
12.3 kB
package store
import (
"database/sql"
"encoding/json"
"errors"
"time"
"github.com/agent-matrix/matrix-runtime/internal/auth"
)
// ---------------------------------------------------------------------------
// Runtimes — a workspace's registered runtime/sandbox planes (incl. HF Spaces).
// ---------------------------------------------------------------------------
// Runtime is a registered execution plane belonging to a workspace.
type Runtime struct {
ID string `json:"id"`
WorkspaceID string `json:"workspace_id"`
Name string `json:"name"`
Mode string `json:"mode"`
Kind string `json:"kind"`
URL string `json:"url"`
HFSpace string `json:"hf_space"`
Region string `json:"region"`
Caps []string `json:"caps"`
Status string `json:"status"`
Version string `json:"version"`
LastSeenAt time.Time `json:"last_seen_at"`
CreatedAt time.Time `json:"created_at"`
}
// RegisterRuntime inserts a runtime for a workspace and returns it plus a
// freshly minted runtime token (shown once) the runtime uses for heartbeats.
func (s *Store) RegisterRuntime(rt Runtime) (*Runtime, string, error) {
rt.ID = auth.NewID("rt_")
if rt.Status == "" {
rt.Status = "online"
}
if rt.Caps == nil {
rt.Caps = []string{}
}
token, err := auth.NewToken()
if err != nil {
return nil, "", err
}
now := nowRFC()
caps, _ := json.Marshal(rt.Caps)
_, err = s.exec(`
INSERT INTO runtimes (id,workspace_id,name,mode,kind,url,hf_space,region,caps_json,status,token_hash,version,last_seen_at,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
rt.ID, rt.WorkspaceID, rt.Name, rt.Mode, rt.Kind, rt.URL, rt.HFSpace, rt.Region, string(caps), rt.Status, hashToken(token), rt.Version, now, now, now)
if err != nil {
return nil, "", err
}
rt.CreatedAt, _ = time.Parse(time.RFC3339, now)
rt.LastSeenAt = rt.CreatedAt
return &rt, token, nil
}
func hashToken(t string) string {
// reuse the password hasher's PBKDF2 would be overkill for a high-entropy
// random token; a fast keyed digest is enough — but we keep it simple and
// store a SHA-style hash via the auth package's id helper is not suitable,
// so use a constant-time-comparable hex of a salted hash.
return auth.FastHash(t)
}
func scanRuntime(row interface{ Scan(...any) error }) (*Runtime, error) {
var rt Runtime
var caps, last, created string
if err := row.Scan(&rt.ID, &rt.WorkspaceID, &rt.Name, &rt.Mode, &rt.Kind, &rt.URL, &rt.HFSpace, &rt.Region, &caps, &rt.Status, &rt.Version, &last, &created); err != nil {
return nil, err
}
_ = json.Unmarshal([]byte(caps), &rt.Caps)
rt.LastSeenAt, _ = time.Parse(time.RFC3339, last)
rt.CreatedAt, _ = time.Parse(time.RFC3339, created)
return &rt, nil
}
const runtimeCols = `id,workspace_id,name,mode,kind,url,hf_space,region,caps_json,status,version,last_seen_at,created_at`
// ListRuntimes returns a workspace's runtimes, newest first, marking those not
// seen within staleAfter as offline.
func (s *Store) ListRuntimes(workspaceID string, staleAfter time.Duration) ([]Runtime, error) {
rows, err := s.query(`SELECT `+runtimeCols+` FROM runtimes WHERE workspace_id=? ORDER BY created_at DESC`, workspaceID)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := []Runtime{}
for rows.Next() {
rt, err := scanRuntime(rows)
if err != nil {
return nil, err
}
if rt.Status != "offline" && staleAfter > 0 && !rt.LastSeenAt.IsZero() && time.Since(rt.LastSeenAt) > staleAfter {
rt.Status = "offline"
}
out = append(out, *rt)
}
return out, rows.Err()
}
// HeartbeatRuntime authenticates a runtime token and updates status/last_seen.
func (s *Store) HeartbeatRuntime(token, status string, caps []string) (*Runtime, error) {
row := s.queryRow(`SELECT `+runtimeCols+` FROM runtimes WHERE token_hash=?`, hashToken(token))
rt, err := scanRuntime(row)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
if status == "" {
status = "online"
}
now := nowRFC()
if caps != nil {
c, _ := json.Marshal(caps)
_, err = s.exec(`UPDATE runtimes SET status=?, caps_json=?, last_seen_at=?, updated_at=? WHERE id=?`, status, string(c), now, now, rt.ID)
rt.Caps = caps
} else {
_, err = s.exec(`UPDATE runtimes SET status=?, last_seen_at=?, updated_at=? WHERE id=?`, status, now, now, rt.ID)
}
rt.Status = status
rt.LastSeenAt, _ = time.Parse(time.RFC3339, now)
return rt, err
}
// ---------------------------------------------------------------------------
// Runtime join tokens — workspace-minted tokens used to register a runtime.
// ---------------------------------------------------------------------------
// JoinToken is the public (non-secret) view of a minted join token.
type JoinToken struct {
ID string `json:"id"`
Label string `json:"label"`
MaxUses int `json:"max_uses"`
Uses int `json:"uses"`
Revoked bool `json:"revoked"`
ExpiresAt string `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
}
// MintJoinToken creates a join token for a workspace and returns the public
// record plus the one-time secret (mxrt_...).
func (s *Store) MintJoinToken(workspaceID, createdBy, label string, maxUses int, ttl time.Duration) (*JoinToken, string, error) {
if maxUses <= 0 {
maxUses = 1
}
raw, err := auth.NewToken()
if err != nil {
return nil, "", err
}
secret := "mxrt_" + raw
now := time.Now().UTC()
exp := ""
if ttl > 0 {
exp = now.Add(ttl).Format(time.RFC3339)
}
jt := &JoinToken{ID: auth.NewID("jt_"), Label: label, MaxUses: maxUses, ExpiresAt: exp, CreatedAt: now}
_, err = s.exec(`INSERT INTO runtime_join_tokens (id,workspace_id,token_hash,label,created_by,max_uses,uses,revoked,expires_at,created_at)
VALUES (?,?,?,?,?,?,0,0,?,?)`,
jt.ID, workspaceID, hashToken(secret), label, createdBy, maxUses, exp, now.Format(time.RFC3339))
if err != nil {
return nil, "", err
}
return jt, secret, nil
}
// RedeemJoinToken validates a join-token secret and returns its workspace,
// incrementing its use counter. Expired/revoked/exhausted tokens are rejected.
func (s *Store) RedeemJoinToken(secret string) (workspaceID string, err error) {
var id, ws, exp string
var maxUses, uses, revoked int
row := s.queryRow(`SELECT id,workspace_id,max_uses,uses,revoked,expires_at FROM runtime_join_tokens WHERE token_hash=?`, hashToken(secret))
if err := row.Scan(&id, &ws, &maxUses, &uses, &revoked, &exp); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", errors.New("invalid join token")
}
return "", err
}
if revoked == 1 {
return "", errors.New("join token revoked")
}
if exp != "" {
if t, e := time.Parse(time.RFC3339, exp); e == nil && time.Now().After(t) {
return "", errors.New("join token expired")
}
}
if uses >= maxUses {
return "", errors.New("join token already used")
}
if _, err := s.exec(`UPDATE runtime_join_tokens SET uses=uses+1 WHERE id=?`, id); err != nil {
return "", err
}
return ws, nil
}
// ListJoinTokens returns a workspace's active (non-revoked) tokens.
func (s *Store) ListJoinTokens(workspaceID string) ([]JoinToken, error) {
rows, err := s.query(`SELECT id,label,max_uses,uses,revoked,expires_at,created_at FROM runtime_join_tokens WHERE workspace_id=? ORDER BY created_at DESC`, workspaceID)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := []JoinToken{}
for rows.Next() {
var jt JoinToken
var rev int
var created string
if err := rows.Scan(&jt.ID, &jt.Label, &jt.MaxUses, &jt.Uses, &rev, &jt.ExpiresAt, &created); err != nil {
return nil, err
}
jt.Revoked = rev == 1
jt.CreatedAt, _ = time.Parse(time.RFC3339, created)
out = append(out, jt)
}
return out, rows.Err()
}
// ---------------------------------------------------------------------------
// Provider credentials — BYO model-provider tokens (encrypted at rest).
// ---------------------------------------------------------------------------
// ProviderCredential is the public view of a stored credential (no secret).
type ProviderCredential struct {
ID string `json:"id"`
Provider string `json:"provider"`
Label string `json:"label"`
Hint string `json:"hint"`
Meta map[string]any `json:"meta"`
CreatedAt time.Time `json:"created_at"`
}
// SetProviderCredential stores (upserts) an encrypted provider token for a
// workspace. The plaintext secret is never persisted.
func (s *Store) SetProviderCredential(workspaceID, createdBy, provider, label, secret string, meta map[string]any) (*ProviderCredential, error) {
if label == "" {
label = "default"
}
enc, err := s.box.Encrypt(secret)
if err != nil {
return nil, err
}
metaB, _ := json.Marshal(meta)
now := nowRFC()
id := auth.NewID("pc_")
_, err = s.exec(`
INSERT INTO provider_credentials (id,workspace_id,provider,label,secret_enc,hint,meta_json,created_by,created_at,updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(workspace_id,provider,label) DO UPDATE SET
secret_enc=excluded.secret_enc, hint=excluded.hint, meta_json=excluded.meta_json, updated_at=excluded.updated_at`,
id, workspaceID, provider, label, enc, auth.Hint(secret), string(metaB), createdBy, now, now)
if err != nil {
return nil, err
}
created, _ := time.Parse(time.RFC3339, now)
return &ProviderCredential{ID: id, Provider: provider, Label: label, Hint: auth.Hint(secret), Meta: meta, CreatedAt: created}, nil
}
// ListProviderCredentials returns a workspace's credentials (without secrets).
func (s *Store) ListProviderCredentials(workspaceID string) ([]ProviderCredential, error) {
rows, err := s.query(`SELECT id,provider,label,hint,meta_json,created_at FROM provider_credentials WHERE workspace_id=? ORDER BY provider`, workspaceID)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := []ProviderCredential{}
for rows.Next() {
var pc ProviderCredential
var meta, created string
if err := rows.Scan(&pc.ID, &pc.Provider, &pc.Label, &pc.Hint, &meta, &created); err != nil {
return nil, err
}
_ = json.Unmarshal([]byte(meta), &pc.Meta)
pc.CreatedAt, _ = time.Parse(time.RFC3339, created)
out = append(out, pc)
}
return out, rows.Err()
}
// ProviderSecret decrypts and returns a workspace's provider token (server-side
// use only — e.g. to call the model gateway on the user's behalf).
func (s *Store) ProviderSecret(workspaceID, provider, label string) (string, error) {
if label == "" {
label = "default"
}
var enc string
row := s.queryRow(`SELECT secret_enc FROM provider_credentials WHERE workspace_id=? AND provider=? AND label=?`, workspaceID, provider, label)
if err := row.Scan(&enc); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", ErrNotFound
}
return "", err
}
return s.box.Decrypt(enc)
}
// ---------------------------------------------------------------------------
// Usage metering — append-only events for free-plan limits and analytics.
// ---------------------------------------------------------------------------
// RecordUsage appends a metering event.
func (s *Store) RecordUsage(workspaceID, userID, runtimeID, kind string, quantity int, meta map[string]any) error {
if quantity <= 0 {
quantity = 1
}
metaB, _ := json.Marshal(meta)
_, err := s.exec(`INSERT INTO usage_events (id,workspace_id,user_id,runtime_id,kind,quantity,meta_json,created_at) VALUES (?,?,?,?,?,?,?,?)`,
auth.NewID("ue_"), workspaceID, userID, runtimeID, kind, quantity, string(metaB), nowRFC())
return err
}
// UsageSince returns summed quantities per kind for a workspace since t.
func (s *Store) UsageSince(workspaceID string, since time.Time) (map[string]int, error) {
rows, err := s.query(`SELECT kind, COALESCE(SUM(quantity),0) FROM usage_events WHERE workspace_id=? AND created_at>=? GROUP BY kind`,
workspaceID, since.UTC().Format(time.RFC3339))
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := map[string]int{}
for rows.Next() {
var k string
var n int
if err := rows.Scan(&k, &n); err != nil {
return nil, err
}
out[k] = n
}
return out, rows.Err()
}