package models import ( "database/sql" "encoding/json" "strings" "time" ) // ── Base ──────────────────────────────────── type BaseModel struct { ID string `json:"id" db:"id"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } // ── Scope Constants ───────────────────────── const ( ScopeGlobal = "global" ScopeTeam = "team" ScopePersonal = "personal" ) // ── Role Constants ────────────────────────── const ( UserRoleUser = "user" UserRoleAdmin = "admin" TeamRoleAdmin = "admin" TeamRoleMember = "member" ) // ── Extension Tier Constants ──────────────── const ( ExtTierBrowser = "browser" ExtTierStarlark = "starlark" ExtTierSidecar = "sidecar" ) type User struct { BaseModel Username string `json:"username" db:"username"` Email string `json:"email" db:"email"` PasswordHash string `json:"-" db:"password_hash"` DisplayName string `json:"display_name,omitempty" db:"display_name"` AvatarURL string `json:"avatar_url,omitempty" db:"avatar_url"` Role string `json:"role" db:"role"` IsActive bool `json:"is_active" db:"is_active"` Settings JSONMap `json:"settings,omitempty" db:"settings"` LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"` AuthSource string `json:"auth_source" db:"auth_source"` ExternalID *string `json:"external_id,omitempty" db:"external_id"` Handle string `json:"handle" db:"handle"` } // ========================================= // TEAMS // ========================================= type Team struct { BaseModel Name string `json:"name" db:"name"` Description string `json:"description,omitempty" db:"description"` CreatedBy string `json:"created_by" db:"created_by"` IsActive bool `json:"is_active" db:"is_active"` Settings JSONMap `json:"settings,omitempty" db:"settings"` MemberCount int `json:"member_count,omitempty"` // computed MyRole string `json:"my_role,omitempty"` // computed, user context } type TeamMember struct { ID string `json:"id" db:"id"` TeamID string `json:"team_id" db:"team_id"` UserID string `json:"user_id" db:"user_id"` Role string `json:"role" db:"role"` JoinedAt string `json:"joined_at" db:"joined_at"` // Joined fields from users table Email string `json:"email,omitempty"` DisplayName string `json:"display_name,omitempty"` Username string `json:"username,omitempty"` UserRole string `json:"user_role,omitempty"` } // ========================================= // GRANTS // ========================================= type Grant struct { ID string `json:"id" db:"id"` PersonaID string `json:"persona_id" db:"persona_id"` GrantType string `json:"grant_type" db:"grant_type"` GrantRef string `json:"grant_ref" db:"grant_ref"` Config JSONMap `json:"config,omitempty" db:"config"` CreatedAt time.Time `json:"created_at" db:"created_at"` } // PLATFORM POLICIES var PolicyDefaults = map[string]string{ "allow_user_byok": "false", "allow_user_personas": "false", "allow_raw_model_access": "false", "allow_registration": "true", "default_user_active": "false", "allow_team_providers": "true", "default_model": "", } // USER MODEL SETTINGS // HiddenEntry identifies a model+provider pair for bulk visibility operations. // CompositeModelKey builds the composite key used for per-provider model preferences. func CompositeModelKey(providerConfigID, modelID string) string { return providerConfigID + ":" + modelID } // CHANNELS // SESSION PARTICIPANTS (v0.24.3) // SessionParticipant is an ephemeral identity for anonymous workflow // channel visitors. Scoped to a single channel, no users row required. // MESSAGES // CHANNEL PARTICIPANTS, MODELS, CURSORS // PERSONA GROUPS (v0.23.0) // HandleFromName generates a URL-safe @mention handle from a display name. // "Veronica Sharpe" → "veronica-sharpe" func HandleFromName(name string) string { h := strings.ToLower(strings.TrimSpace(name)) h = strings.Map(func(r rune) rune { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' { return r } if r == ' ' || r == '_' { return '-' } return -1 }, h) // Collapse multiple hyphens for strings.Contains(h, "--") { h = strings.ReplaceAll(h, "--", "-") } h = strings.Trim(h, "-") if len(h) > 50 { h = h[:50] } return h } // ORGANIZATION // ProjectPatch holds optional fields for updating a project. // ProjectChannel represents a channel's membership in a project. // ProjectKB represents a KB's association with a project. // ProjectNote represents a note's association with a project. // NOTES // NoteLink represents a directed link extracted from [[wikilink]] syntax. // ExportNoteLink is a note_link with source_note_id included (for export). // NoteLinkResult represents a backlink — a note that links to a given note. // NoteGraphNode is a lightweight note representation for graph display. // NoteGraphEdge is a resolved link between two notes. // NoteGraphDangling is an unresolved [[link]] reference. // NoteGraph is the full graph topology for a user's notes. // ATTACHMENTS // FILES // FileOrigin constants // ========================================= // AUDIT LOG // ========================================= type AuditEntry struct { ID string `json:"id" db:"id"` ActorID *string `json:"actor_id,omitempty" db:"actor_id"` ActorName *string `json:"actor_name,omitempty" db:"-"` // resolved via JOIN, not a column Action string `json:"action" db:"action"` ResourceType string `json:"resource_type" db:"resource_type"` ResourceID string `json:"resource_id,omitempty" db:"resource_id"` Metadata JSONMap `json:"metadata,omitempty" db:"metadata"` IPAddress string `json:"ip_address,omitempty" db:"ip_address"` UserAgent string `json:"user_agent,omitempty" db:"user_agent"` CreatedAt time.Time `json:"created_at" db:"created_at"` } // USAGE TRACKING // MODEL PRICING // VIEW MODELS (computed, not stored) // UserModel is the view model returned by the capability resolver. // Combines catalog entries + Personas for the frontend. // ========================================= // JSON HELPERS // ========================================= // JSONMap scans from/to JSONB columns. type JSONMap map[string]interface{} func (m *JSONMap) Scan(src interface{}) error { if src == nil { *m = nil return nil } var source []byte switch v := src.(type) { case []byte: source = v case string: source = []byte(v) default: return nil } result := make(JSONMap) if err := json.Unmarshal(source, &result); err != nil { return err } *m = result return nil } // Extension tier constants type Extension struct { ID string `json:"id" db:"id"` ExtID string `json:"ext_id" db:"ext_id"` // manifest id Name string `json:"name" db:"name"` Version string `json:"version" db:"version"` Tier string `json:"tier" db:"tier"` Description string `json:"description" db:"description"` Author string `json:"author" db:"author"` Manifest json.RawMessage `json:"manifest" db:"manifest"` IsSystem bool `json:"is_system" db:"is_system"` IsEnabled bool `json:"is_enabled" db:"is_enabled"` Scope string `json:"scope" db:"scope"` TeamID *string `json:"team_id,omitempty" db:"team_id"` InstalledBy *string `json:"installed_by,omitempty" db:"installed_by"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } // ExtensionUserSettings stores per-user overrides for an extension. type ExtensionUserSettings struct { ExtensionID string `json:"extension_id" db:"extension_id"` UserID string `json:"user_id" db:"user_id"` Settings json.RawMessage `json:"settings" db:"settings"` IsEnabled bool `json:"is_enabled" db:"is_enabled"` } // UserExtension combines extension info with user-specific settings for API responses. type UserExtension struct { Extension UserEnabled *bool `json:"user_enabled,omitempty"` UserSettings *json.RawMessage `json:"user_settings,omitempty"` } func NullString(s *string) sql.NullString { if s == nil { return sql.NullString{} } return sql.NullString{String: *s, Valid: true} } func NullFloat(f *float64) sql.NullFloat64 { if f == nil { return sql.NullFloat64{} } return sql.NullFloat64{Float64: *f, Valid: true} } func NullInt(i *int) sql.NullInt64 { if i == nil { return sql.NullInt64{} } return sql.NullInt64{Int64: int64(*i), Valid: true} } func StringPtr(s string) *string { return &s } func Float64Ptr(f float64) *float64 { return &f } func IntPtr(i int) *int { return &i } func BoolPtr(b bool) *bool { return &b } // ========================================= // GROUPS (v0.16.0) // ========================================= // Group scope constants (reuses ScopeGlobal, ScopeTeam from above) // ResourceType constants for resource_grants const ( ResourceTypePersona = "persona" ResourceTypeKnowledgeBase = "knowledge_base" ) // GrantScope constants for resource_grants const ( GrantScopeTeamOnly = "team_only" GrantScopeGlobal = "global" GrantScopeGroups = "groups" ) // Group is an access-control list. Decouples resource visibility from teams. type Group struct { BaseModel Name string `json:"name" db:"name"` Description string `json:"description" db:"description"` Scope string `json:"scope" db:"scope"` // global, team TeamID *string `json:"team_id,omitempty" db:"team_id"` CreatedBy *string `json:"created_by,omitempty" db:"created_by"` Source string `json:"source" db:"source"` // manual (default), oidc, system Permissions []string `json:"permissions" db:"permissions"` TokenBudgetDaily *int64 `json:"token_budget_daily,omitempty" db:"token_budget_daily"` TokenBudgetMonthly *int64 `json:"token_budget_monthly,omitempty" db:"token_budget_monthly"` AllowedModels []string `json:"allowed_models,omitempty" db:"allowed_models"` // nil = unrestricted MemberCount int `json:"member_count,omitempty"` // computed, not a DB column } // GroupPatch holds optional fields for updating a group. type GroupPatch struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` Permissions *[]string `json:"permissions,omitempty"` TokenBudgetDaily *int64 `json:"token_budget_daily,omitempty"` TokenBudgetMonthly *int64 `json:"token_budget_monthly,omitempty"` AllowedModels *[]string `json:"allowed_models,omitempty"` // &[]string{} = restrict to none; nil = no change ClearBudgetDaily bool `json:"clear_budget_daily,omitempty"` ClearBudgetMonthly bool `json:"clear_budget_monthly,omitempty"` ClearAllowedModels bool `json:"clear_allowed_models,omitempty"` } // GroupMember links a user to a group. type GroupMember struct { ID string `json:"id" db:"id"` GroupID string `json:"group_id" db:"group_id"` UserID string `json:"user_id" db:"user_id"` AddedBy string `json:"added_by" db:"added_by"` AddedAt time.Time `json:"added_at" db:"added_at"` // Joined fields from users table (for list responses) Username string `json:"username,omitempty"` Email string `json:"email,omitempty"` DisplayName string `json:"display_name,omitempty"` } // ResourceGrant controls who can USE a resource (personas, KBs) via groups. // One row per resource. Not to be confused with persona_grants (what a Persona can DO). type ResourceGrant struct { BaseModel ResourceType string `json:"resource_type" db:"resource_type"` ResourceID string `json:"resource_id" db:"resource_id"` GrantScope string `json:"grant_scope" db:"grant_scope"` GrantedGroups []string `json:"granted_groups" db:"granted_groups"` // UUID array CreatedBy string `json:"created_by" db:"created_by"` } // KnowledgeBase is a named collection of documents with vector embeddings. // PersonaKB binds a knowledge base to a persona. // KBDocument is a single uploaded file within a knowledge base. // KBChunk is a text segment from a document with its embedding vector. // KBSearchResult is a single result from similarity search. // ChannelKB represents a knowledge base linked to a channel. // PROVIDER HEALTH (v0.22.0) // ProviderStatus represents the derived health state. // AvgLatencyMs returns the average latency, or 0 if no requests. // ErrorRate returns the error fraction, or 0 if no requests. // CAPABILITY OVERRIDES (v0.22.0) // CapabilityOverride is an admin correction for a model's capabilities. // ROUTING POLICIES (v0.22.2) // RoutingPolicy is one routing rule that controls how requests are // dispatched to provider configs. Stored in the routing_policies table.