first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# platform/model
|
||||
|
||||
Database models live here and are the source of truth for table structure. Migrations must reference these models or be kept in sync with them.
|
||||
|
||||
Required model groups:
|
||||
|
||||
- users and roles.
|
||||
- game management plugins and installed plugin versions.
|
||||
- server instances and config versions.
|
||||
- AI providers and secret references.
|
||||
- run endpoints and capabilities.
|
||||
- jobs and job events.
|
||||
- artifacts and chunks.
|
||||
- log streams and ingestion cursors.
|
||||
- audit events.
|
||||
|
||||
Every implemented database model must include field comments, JSON/database tags, and an explicit table name function or equivalent mapping in the chosen stack.
|
||||
@@ -0,0 +1,677 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
// ID is the stable platform user identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// DisplayName is the user-visible account name.
|
||||
DisplayName string `json:"displayName" db:"display_name"`
|
||||
// Email is the optional login email.
|
||||
Email string `json:"email,omitempty" db:"email"`
|
||||
// Status is the user lifecycle status.
|
||||
Status domain.UserStatus `json:"status" db:"status"`
|
||||
// Roles stores assigned role keys.
|
||||
Roles []string `json:"roles" db:"roles"`
|
||||
// PasswordHash stores a platform-owned password verifier.
|
||||
PasswordHash string `json:"passwordHash" db:"password_hash"`
|
||||
// Profile stores bounded user contact metadata.
|
||||
Profile domain.UserProfile `json:"profile" db:"profile"`
|
||||
// Theme stores the user's persisted console theme preference.
|
||||
Theme domain.UserThemePreference `json:"theme" db:"theme"`
|
||||
// CreatedAt is the record creation timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
// UpdatedAt is the last update timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
}
|
||||
|
||||
func (User) TableName() string { return "users" }
|
||||
|
||||
type AIProvider struct {
|
||||
// ID is the stable AI provider identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// Name is the display name shown to operators.
|
||||
Name string `json:"name" db:"name"`
|
||||
// Kind identifies the provider protocol family.
|
||||
Kind domain.AIProviderKind `json:"kind" db:"kind"`
|
||||
// BaseURL is the provider or relay endpoint.
|
||||
BaseURL string `json:"baseUrl" db:"base_url"`
|
||||
// APIKeyRef references secret storage and never stores raw key material.
|
||||
APIKeyRef string `json:"apiKeyRef" db:"api_key_ref"`
|
||||
// Models lists allowed model identifiers.
|
||||
Models []string `json:"models" db:"models"`
|
||||
// DefaultModel is the optional default model identifier.
|
||||
DefaultModel string `json:"defaultModel,omitempty" db:"default_model"`
|
||||
// RelayMode controls direct, relay, or local routing.
|
||||
RelayMode domain.AIRelayMode `json:"relayMode" db:"relay_mode"`
|
||||
// TimeoutMS is the provider request timeout in milliseconds.
|
||||
TimeoutMS int `json:"timeoutMs" db:"timeout_ms"`
|
||||
// Status is the provider lifecycle status.
|
||||
Status domain.AIProviderStatus `json:"status" db:"status"`
|
||||
// RedactionPolicy identifies prompt/input/output redaction behavior.
|
||||
RedactionPolicy string `json:"redactionPolicy" db:"redaction_policy"`
|
||||
}
|
||||
|
||||
func (AIProvider) TableName() string { return "ai_providers" }
|
||||
|
||||
type PluginPermissions struct {
|
||||
// AI allows platform-mediated AI requests.
|
||||
AI bool `json:"ai" db:"ai"`
|
||||
// Logs allows scoped log queries.
|
||||
Logs bool `json:"logs" db:"logs"`
|
||||
// Files allows scoped file/artifact operations.
|
||||
Files bool `json:"files" db:"files"`
|
||||
// Jobs allows lifecycle job dispatch.
|
||||
Jobs bool `json:"jobs" db:"jobs"`
|
||||
// Artifacts allows artifact metadata and transfer references.
|
||||
Artifacts bool `json:"artifacts" db:"artifacts"`
|
||||
}
|
||||
|
||||
type PluginLifecycleActions struct {
|
||||
// Install references the install action contract.
|
||||
Install string `json:"install" db:"install"`
|
||||
// Start references the start action contract.
|
||||
Start string `json:"start" db:"start"`
|
||||
// Stop references the stop action contract.
|
||||
Stop string `json:"stop" db:"stop"`
|
||||
// Restart references the optional restart action contract.
|
||||
Restart string `json:"restart,omitempty" db:"restart"`
|
||||
// Status references the optional status action contract.
|
||||
Status string `json:"status,omitempty" db:"status"`
|
||||
}
|
||||
|
||||
type GamePluginPage struct {
|
||||
// Key is stable within the plugin manifest.
|
||||
Key string `json:"key" db:"key"`
|
||||
// Title is the page label shown by platform clients.
|
||||
Title string `json:"title" db:"title"`
|
||||
// Path is the plugin-local page route.
|
||||
Path string `json:"path" db:"path"`
|
||||
// Permissions lists scoped platform bridge permissions required by the page.
|
||||
Permissions []string `json:"permissions" db:"permissions"`
|
||||
}
|
||||
|
||||
type GamePlugin struct {
|
||||
// ID is the installed game management plugin identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// Name is the plugin display name.
|
||||
Name string `json:"name" db:"name"`
|
||||
// Description is bounded marketplace metadata from the manifest.
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
// Version is the installed plugin version.
|
||||
Version string `json:"version" db:"version"`
|
||||
// ServerType is the game/server type key this plugin manages.
|
||||
ServerType string `json:"serverType" db:"server_type"`
|
||||
// ServerDisplayName is the user-visible server type name.
|
||||
ServerDisplayName string `json:"serverDisplayName,omitempty" db:"server_display_name"`
|
||||
// SupportedOS lists run operating systems declared by the plugin.
|
||||
SupportedOS []string `json:"supportedOs" db:"supported_os"`
|
||||
// ManifestRef points to the immutable manifest artifact.
|
||||
ManifestRef string `json:"manifestRef" db:"manifest_ref"`
|
||||
// CreateFormSchemaRef points to the create form schema artifact.
|
||||
CreateFormSchemaRef string `json:"createFormSchemaRef" db:"create_form_schema_ref"`
|
||||
// RequiredRunCapabilities lists run capabilities needed by this plugin.
|
||||
RequiredRunCapabilities []string `json:"requiredRunCapabilities" db:"required_run_capabilities"`
|
||||
// DeclaredPermissions lists scoped manifest permission keys.
|
||||
DeclaredPermissions []string `json:"declaredPermissions" db:"declared_permissions"`
|
||||
// Permissions declares platform-mediated plugin abilities.
|
||||
Permissions PluginPermissions `json:"permissions" db:"permissions"`
|
||||
// LifecycleActions stores manifest lifecycle action references.
|
||||
LifecycleActions PluginLifecycleActions `json:"lifecycleActions" db:"lifecycle_actions"`
|
||||
// Pages stores plugin-local page metadata.
|
||||
Pages []GamePluginPage `json:"pages" db:"pages"`
|
||||
// Tags stores bounded catalog tags.
|
||||
Tags []string `json:"tags" db:"tags"`
|
||||
// AIPurposes stores platform-mediated AI usage purposes.
|
||||
AIPurposes []string `json:"aiPurposes" db:"ai_purposes"`
|
||||
// ValidationViolations stores safe validation findings for invalid plugins.
|
||||
ValidationViolations []string `json:"validationViolations" db:"validation_violations"`
|
||||
// Status is the plugin lifecycle status.
|
||||
Status domain.GamePluginStatus `json:"status" db:"status"`
|
||||
}
|
||||
|
||||
func (GamePlugin) TableName() string { return "game_plugins" }
|
||||
|
||||
type ServerInstance struct {
|
||||
// ID is the stable server instance identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// PluginID references the installed game management plugin.
|
||||
PluginID string `json:"pluginId" db:"plugin_id"`
|
||||
// PluginVersion records the plugin version used for creation or reconcile.
|
||||
PluginVersion string `json:"pluginVersion" db:"plugin_version"`
|
||||
// RunEndpointID references the selected run endpoint.
|
||||
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
|
||||
// Name is the server display name.
|
||||
Name string `json:"name" db:"name"`
|
||||
// OwnerUserID identifies the server owner account.
|
||||
OwnerUserID string `json:"ownerUserId" db:"owner_user_id"`
|
||||
// AdminUserIDs identifies server-scoped administrator accounts.
|
||||
AdminUserIDs []string `json:"adminUserIds" db:"admin_user_ids"`
|
||||
// State is the server lifecycle state.
|
||||
State domain.ServerInstanceState `json:"state" db:"state"`
|
||||
// ConfigVersion is the platform-managed optimistic concurrency version.
|
||||
ConfigVersion int `json:"configVersion" db:"config_version"`
|
||||
// CreatedAt is the record creation timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
// UpdatedAt is the last update timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
}
|
||||
|
||||
func (ServerInstance) TableName() string { return "server_instances" }
|
||||
|
||||
type RunCapacity struct {
|
||||
// MaxJobs is the advertised job concurrency.
|
||||
MaxJobs int `json:"maxJobs" db:"max_jobs"`
|
||||
// RunningJobs is the current running job count.
|
||||
RunningJobs int `json:"runningJobs" db:"running_jobs"`
|
||||
// QueuedJobs is the current queued job count.
|
||||
QueuedJobs int `json:"queuedJobs" db:"queued_jobs"`
|
||||
// Summary is a bounded human-readable capacity summary.
|
||||
Summary string `json:"summary,omitempty" db:"summary"`
|
||||
}
|
||||
|
||||
type RunEndpoint struct {
|
||||
// ID is the stable run endpoint identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// DisplayName is the visible executor name.
|
||||
DisplayName string `json:"displayName" db:"display_name"`
|
||||
// Version is the run binary version.
|
||||
Version string `json:"version" db:"version"`
|
||||
// Status is the current endpoint status.
|
||||
Status domain.RunEndpointStatus `json:"status" db:"status"`
|
||||
// Capabilities lists advertised run capability keys.
|
||||
Capabilities []string `json:"capabilities" db:"capabilities"`
|
||||
// Capacity stores current queue and resource summary.
|
||||
Capacity RunCapacity `json:"capacity" db:"capacity"`
|
||||
// LastHeartbeatAt is the latest control heartbeat timestamp.
|
||||
LastHeartbeatAt time.Time `json:"lastHeartbeatAt" db:"last_heartbeat_at"`
|
||||
}
|
||||
|
||||
func (RunEndpoint) TableName() string { return "run_endpoints" }
|
||||
|
||||
type JobProgress struct {
|
||||
// Percent is bounded from 0 to 100.
|
||||
Percent int `json:"percent" db:"percent"`
|
||||
// Message is a bounded progress summary.
|
||||
Message string `json:"message,omitempty" db:"message"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
// ID is the stable job identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// ServerInstanceID optionally references the target server.
|
||||
ServerInstanceID string `json:"serverInstanceId,omitempty" db:"server_instance_id"`
|
||||
// RunEndpointID references the target run endpoint.
|
||||
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
|
||||
// Capability is the requested run capability key.
|
||||
Capability string `json:"capability" db:"capability"`
|
||||
// TargetKey is a logical config/file key, never a host path.
|
||||
TargetKey string `json:"targetKey,omitempty" db:"target_key"`
|
||||
// InputRef points to a platform-scoped write payload or artifact.
|
||||
InputRef string `json:"inputRef,omitempty" db:"input_ref"`
|
||||
// IdempotencyKey detects duplicate job requests per run endpoint.
|
||||
IdempotencyKey string `json:"idempotencyKey" db:"idempotency_key"`
|
||||
// State is the job lifecycle state.
|
||||
State domain.JobState `json:"state" db:"state"`
|
||||
// Progress stores bounded progress metadata.
|
||||
Progress JobProgress `json:"progress" db:"progress"`
|
||||
// ResultRef references the terminal result artifact or summary.
|
||||
ResultRef string `json:"resultRef,omitempty" db:"result_ref"`
|
||||
// CreatedAt is the record creation timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
// UpdatedAt is the last update timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
}
|
||||
|
||||
func (Job) TableName() string { return "jobs" }
|
||||
|
||||
type Artifact struct {
|
||||
// ID is the stable artifact identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// OwnerKind identifies the owning resource class.
|
||||
OwnerKind domain.ArtifactOwnerKind `json:"ownerKind" db:"owner_kind"`
|
||||
// OwnerID identifies the owning resource.
|
||||
OwnerID string `json:"ownerId" db:"owner_id"`
|
||||
// SizeBytes stores the expected or final artifact size.
|
||||
SizeBytes int64 `json:"sizeBytes" db:"size_bytes"`
|
||||
// Checksum stores the final checksum.
|
||||
Checksum string `json:"checksum" db:"checksum"`
|
||||
// State is the artifact lifecycle state.
|
||||
State domain.ArtifactState `json:"state" db:"state"`
|
||||
// CreatedAt is the record creation timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
// UpdatedAt is the last update timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
}
|
||||
|
||||
func (Artifact) TableName() string { return "artifacts" }
|
||||
|
||||
type LogStream struct {
|
||||
// ID is the stable log stream identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// ServerInstanceID references the target server.
|
||||
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
|
||||
// Source identifies process, file, plugin, or custom source.
|
||||
Source domain.LogStreamSource `json:"source" db:"source"`
|
||||
// StreamKey is stable within the server instance.
|
||||
StreamKey string `json:"streamKey" db:"stream_key"`
|
||||
// LatestSeq is the latest accepted sequence number.
|
||||
LatestSeq uint64 `json:"latestSeq" db:"latest_seq"`
|
||||
// StorageBackend identifies the log body backend.
|
||||
StorageBackend domain.LogStorageBackend `json:"storageBackend" db:"storage_backend"`
|
||||
// RetentionPolicy identifies retention behavior.
|
||||
RetentionPolicy string `json:"retentionPolicy" db:"retention_policy"`
|
||||
// CreatedAt is the record creation timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
// UpdatedAt is the last update timestamp.
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
}
|
||||
|
||||
func (LogStream) TableName() string { return "log_streams" }
|
||||
|
||||
type AuditEvent struct {
|
||||
// ID is the stable audit event identifier.
|
||||
ID string `json:"id" db:"id"`
|
||||
// ActorID references the user or system actor.
|
||||
ActorID string `json:"actorId" db:"actor_id"`
|
||||
// Action is the stable action key.
|
||||
Action string `json:"action" db:"action"`
|
||||
// ResourceKind identifies the audited resource type.
|
||||
ResourceKind string `json:"resourceKind" db:"resource_kind"`
|
||||
// ResourceID identifies the audited resource.
|
||||
ResourceID string `json:"resourceId" db:"resource_id"`
|
||||
// Result is the audit outcome.
|
||||
Result domain.AuditResult `json:"result" db:"result"`
|
||||
// Summary is a bounded redacted summary.
|
||||
Summary string `json:"summary" db:"summary"`
|
||||
// CreatedAt is the audit timestamp.
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
}
|
||||
|
||||
func (AuditEvent) TableName() string { return "audit_events" }
|
||||
|
||||
func UserFromDomain(user domain.User) User {
|
||||
user = domain.CopyUser(user)
|
||||
return User{
|
||||
ID: user.ID,
|
||||
DisplayName: user.DisplayName,
|
||||
Email: user.Email,
|
||||
Status: user.Status,
|
||||
Roles: user.Roles,
|
||||
PasswordHash: user.PasswordHash,
|
||||
Profile: user.Profile,
|
||||
Theme: user.Theme,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (user User) ToDomain() domain.User {
|
||||
return domain.User{
|
||||
ID: user.ID,
|
||||
DisplayName: user.DisplayName,
|
||||
Email: user.Email,
|
||||
Status: user.Status,
|
||||
Roles: domain.CopyStringSlice(user.Roles),
|
||||
PasswordHash: user.PasswordHash,
|
||||
Profile: user.Profile,
|
||||
Theme: user.Theme,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func AIProviderFromDomain(provider domain.AIProvider) AIProvider {
|
||||
provider = domain.CopyAIProvider(provider)
|
||||
return AIProvider{
|
||||
ID: provider.ID,
|
||||
Name: provider.Name,
|
||||
Kind: provider.Kind,
|
||||
BaseURL: provider.BaseURL,
|
||||
APIKeyRef: provider.APIKeyRef,
|
||||
Models: provider.Models,
|
||||
DefaultModel: provider.DefaultModel,
|
||||
RelayMode: provider.RelayMode,
|
||||
TimeoutMS: provider.TimeoutMS,
|
||||
Status: provider.Status,
|
||||
RedactionPolicy: provider.RedactionPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider AIProvider) ToDomain() domain.AIProvider {
|
||||
return domain.AIProvider{
|
||||
ID: provider.ID,
|
||||
Name: provider.Name,
|
||||
Kind: provider.Kind,
|
||||
BaseURL: provider.BaseURL,
|
||||
APIKeyRef: provider.APIKeyRef,
|
||||
Models: domain.CopyStringSlice(provider.Models),
|
||||
DefaultModel: provider.DefaultModel,
|
||||
RelayMode: provider.RelayMode,
|
||||
TimeoutMS: provider.TimeoutMS,
|
||||
Status: provider.Status,
|
||||
RedactionPolicy: provider.RedactionPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
func GamePluginFromDomain(plugin domain.GamePlugin) GamePlugin {
|
||||
plugin = domain.CopyGamePlugin(plugin)
|
||||
return GamePlugin{
|
||||
ID: plugin.ID,
|
||||
Name: plugin.Name,
|
||||
Description: plugin.Description,
|
||||
Version: plugin.Version,
|
||||
ServerType: plugin.ServerType,
|
||||
ServerDisplayName: plugin.ServerDisplayName,
|
||||
SupportedOS: plugin.SupportedOS,
|
||||
ManifestRef: plugin.ManifestRef,
|
||||
CreateFormSchemaRef: plugin.CreateFormSchemaRef,
|
||||
RequiredRunCapabilities: plugin.RequiredRunCapabilities,
|
||||
DeclaredPermissions: plugin.DeclaredPermissions,
|
||||
Permissions: permissionsFromDomain(plugin.Permissions),
|
||||
LifecycleActions: lifecycleActionsFromDomain(plugin.LifecycleActions),
|
||||
Pages: pagesFromDomain(plugin.Pages),
|
||||
Tags: plugin.Tags,
|
||||
AIPurposes: plugin.AIPurposes,
|
||||
ValidationViolations: plugin.ValidationViolations,
|
||||
Status: plugin.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin GamePlugin) ToDomain() domain.GamePlugin {
|
||||
return domain.GamePlugin{
|
||||
ID: plugin.ID,
|
||||
Name: plugin.Name,
|
||||
Description: plugin.Description,
|
||||
Version: plugin.Version,
|
||||
ServerType: plugin.ServerType,
|
||||
ServerDisplayName: plugin.ServerDisplayName,
|
||||
SupportedOS: domain.CopyStringSlice(plugin.SupportedOS),
|
||||
ManifestRef: plugin.ManifestRef,
|
||||
CreateFormSchemaRef: plugin.CreateFormSchemaRef,
|
||||
RequiredRunCapabilities: domain.CopyStringSlice(plugin.RequiredRunCapabilities),
|
||||
DeclaredPermissions: domain.CopyStringSlice(plugin.DeclaredPermissions),
|
||||
Permissions: plugin.Permissions.ToDomain(),
|
||||
LifecycleActions: plugin.LifecycleActions.ToDomain(),
|
||||
Pages: pagesToDomain(plugin.Pages),
|
||||
Tags: domain.CopyStringSlice(plugin.Tags),
|
||||
AIPurposes: domain.CopyStringSlice(plugin.AIPurposes),
|
||||
ValidationViolations: domain.CopyStringSlice(plugin.ValidationViolations),
|
||||
Status: plugin.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (actions PluginLifecycleActions) ToDomain() domain.PluginLifecycleActions {
|
||||
return domain.PluginLifecycleActions{
|
||||
Install: actions.Install,
|
||||
Start: actions.Start,
|
||||
Stop: actions.Stop,
|
||||
Restart: actions.Restart,
|
||||
Status: actions.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func lifecycleActionsFromDomain(actions domain.PluginLifecycleActions) PluginLifecycleActions {
|
||||
return PluginLifecycleActions{
|
||||
Install: actions.Install,
|
||||
Start: actions.Start,
|
||||
Stop: actions.Stop,
|
||||
Restart: actions.Restart,
|
||||
Status: actions.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func pagesToDomain(pages []GamePluginPage) []domain.GamePluginPage {
|
||||
if pages == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]domain.GamePluginPage, len(pages))
|
||||
for i, page := range pages {
|
||||
out[i] = domain.GamePluginPage{
|
||||
Key: page.Key,
|
||||
Title: page.Title,
|
||||
Path: page.Path,
|
||||
Permissions: domain.CopyStringSlice(page.Permissions),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func pagesFromDomain(pages []domain.GamePluginPage) []GamePluginPage {
|
||||
if pages == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]GamePluginPage, len(pages))
|
||||
for i, page := range pages {
|
||||
out[i] = GamePluginPage{
|
||||
Key: page.Key,
|
||||
Title: page.Title,
|
||||
Path: page.Path,
|
||||
Permissions: domain.CopyStringSlice(page.Permissions),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (permissions PluginPermissions) ToDomain() domain.PluginPermissions {
|
||||
return domain.PluginPermissions{
|
||||
AI: permissions.AI,
|
||||
Logs: permissions.Logs,
|
||||
Files: permissions.Files,
|
||||
Jobs: permissions.Jobs,
|
||||
Artifacts: permissions.Artifacts,
|
||||
}
|
||||
}
|
||||
|
||||
func permissionsFromDomain(permissions domain.PluginPermissions) PluginPermissions {
|
||||
return PluginPermissions{
|
||||
AI: permissions.AI,
|
||||
Logs: permissions.Logs,
|
||||
Files: permissions.Files,
|
||||
Jobs: permissions.Jobs,
|
||||
Artifacts: permissions.Artifacts,
|
||||
}
|
||||
}
|
||||
|
||||
func ServerInstanceFromDomain(instance domain.ServerInstance) ServerInstance {
|
||||
return ServerInstance{
|
||||
ID: instance.ID,
|
||||
PluginID: instance.PluginID,
|
||||
PluginVersion: instance.PluginVersion,
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
Name: instance.Name,
|
||||
State: instance.State,
|
||||
ConfigVersion: instance.ConfigVersion,
|
||||
CreatedAt: instance.CreatedAt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (instance ServerInstance) ToDomain() domain.ServerInstance {
|
||||
return domain.ServerInstance{
|
||||
ID: instance.ID,
|
||||
PluginID: instance.PluginID,
|
||||
PluginVersion: instance.PluginVersion,
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
Name: instance.Name,
|
||||
State: instance.State,
|
||||
ConfigVersion: instance.ConfigVersion,
|
||||
CreatedAt: instance.CreatedAt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func RunEndpointFromDomain(endpoint domain.RunEndpoint) RunEndpoint {
|
||||
endpoint = domain.CopyRunEndpoint(endpoint)
|
||||
return RunEndpoint{
|
||||
ID: endpoint.ID,
|
||||
DisplayName: endpoint.DisplayName,
|
||||
Version: endpoint.Version,
|
||||
Status: endpoint.Status,
|
||||
Capabilities: endpoint.Capabilities,
|
||||
Capacity: capacityFromDomain(endpoint.Capacity),
|
||||
LastHeartbeatAt: endpoint.LastHeartbeatAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (endpoint RunEndpoint) ToDomain() domain.RunEndpoint {
|
||||
return domain.RunEndpoint{
|
||||
ID: endpoint.ID,
|
||||
DisplayName: endpoint.DisplayName,
|
||||
Version: endpoint.Version,
|
||||
Status: endpoint.Status,
|
||||
Capabilities: domain.CopyStringSlice(endpoint.Capabilities),
|
||||
Capacity: endpoint.Capacity.ToDomain(),
|
||||
LastHeartbeatAt: endpoint.LastHeartbeatAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (capacity RunCapacity) ToDomain() domain.RunCapacity {
|
||||
return domain.RunCapacity{
|
||||
MaxJobs: capacity.MaxJobs,
|
||||
RunningJobs: capacity.RunningJobs,
|
||||
QueuedJobs: capacity.QueuedJobs,
|
||||
Summary: capacity.Summary,
|
||||
}
|
||||
}
|
||||
|
||||
func capacityFromDomain(capacity domain.RunCapacity) RunCapacity {
|
||||
return RunCapacity{
|
||||
MaxJobs: capacity.MaxJobs,
|
||||
RunningJobs: capacity.RunningJobs,
|
||||
QueuedJobs: capacity.QueuedJobs,
|
||||
Summary: capacity.Summary,
|
||||
}
|
||||
}
|
||||
|
||||
func JobFromDomain(job domain.Job) Job {
|
||||
return Job{
|
||||
ID: job.ID,
|
||||
ServerInstanceID: job.ServerInstanceID,
|
||||
RunEndpointID: job.RunEndpointID,
|
||||
Capability: job.Capability,
|
||||
TargetKey: job.TargetKey,
|
||||
InputRef: job.InputRef,
|
||||
IdempotencyKey: job.IdempotencyKey,
|
||||
State: job.State,
|
||||
Progress: progressFromDomain(job.Progress),
|
||||
ResultRef: job.ResultRef,
|
||||
CreatedAt: job.CreatedAt,
|
||||
UpdatedAt: job.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (job Job) ToDomain() domain.Job {
|
||||
return domain.Job{
|
||||
ID: job.ID,
|
||||
ServerInstanceID: job.ServerInstanceID,
|
||||
RunEndpointID: job.RunEndpointID,
|
||||
Capability: job.Capability,
|
||||
TargetKey: job.TargetKey,
|
||||
InputRef: job.InputRef,
|
||||
IdempotencyKey: job.IdempotencyKey,
|
||||
State: job.State,
|
||||
Progress: job.Progress.ToDomain(),
|
||||
ResultRef: job.ResultRef,
|
||||
CreatedAt: job.CreatedAt,
|
||||
UpdatedAt: job.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (progress JobProgress) ToDomain() domain.JobProgress {
|
||||
return domain.JobProgress{
|
||||
Percent: progress.Percent,
|
||||
Message: progress.Message,
|
||||
}
|
||||
}
|
||||
|
||||
func progressFromDomain(progress domain.JobProgress) JobProgress {
|
||||
return JobProgress{
|
||||
Percent: progress.Percent,
|
||||
Message: progress.Message,
|
||||
}
|
||||
}
|
||||
|
||||
func ArtifactFromDomain(artifact domain.Artifact) Artifact {
|
||||
return Artifact{
|
||||
ID: artifact.ID,
|
||||
OwnerKind: artifact.OwnerKind,
|
||||
OwnerID: artifact.OwnerID,
|
||||
SizeBytes: artifact.SizeBytes,
|
||||
Checksum: artifact.Checksum,
|
||||
State: artifact.State,
|
||||
CreatedAt: artifact.CreatedAt,
|
||||
UpdatedAt: artifact.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (artifact Artifact) ToDomain() domain.Artifact {
|
||||
return domain.Artifact{
|
||||
ID: artifact.ID,
|
||||
OwnerKind: artifact.OwnerKind,
|
||||
OwnerID: artifact.OwnerID,
|
||||
SizeBytes: artifact.SizeBytes,
|
||||
Checksum: artifact.Checksum,
|
||||
State: artifact.State,
|
||||
CreatedAt: artifact.CreatedAt,
|
||||
UpdatedAt: artifact.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func LogStreamFromDomain(stream domain.LogStream) LogStream {
|
||||
return LogStream{
|
||||
ID: stream.ID,
|
||||
ServerInstanceID: stream.ServerInstanceID,
|
||||
Source: stream.Source,
|
||||
StreamKey: stream.StreamKey,
|
||||
LatestSeq: stream.LatestSeq,
|
||||
StorageBackend: stream.StorageBackend,
|
||||
RetentionPolicy: stream.RetentionPolicy,
|
||||
CreatedAt: stream.CreatedAt,
|
||||
UpdatedAt: stream.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (stream LogStream) ToDomain() domain.LogStream {
|
||||
return domain.LogStream{
|
||||
ID: stream.ID,
|
||||
ServerInstanceID: stream.ServerInstanceID,
|
||||
Source: stream.Source,
|
||||
StreamKey: stream.StreamKey,
|
||||
LatestSeq: stream.LatestSeq,
|
||||
StorageBackend: stream.StorageBackend,
|
||||
RetentionPolicy: stream.RetentionPolicy,
|
||||
CreatedAt: stream.CreatedAt,
|
||||
UpdatedAt: stream.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func AuditEventFromDomain(event domain.AuditEvent) AuditEvent {
|
||||
return AuditEvent{
|
||||
ID: event.ID,
|
||||
ActorID: event.ActorID,
|
||||
Action: event.Action,
|
||||
ResourceKind: event.ResourceKind,
|
||||
ResourceID: event.ResourceID,
|
||||
Result: event.Result,
|
||||
Summary: event.Summary,
|
||||
CreatedAt: event.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (event AuditEvent) ToDomain() domain.AuditEvent {
|
||||
return domain.AuditEvent{
|
||||
ID: event.ID,
|
||||
ActorID: event.ActorID,
|
||||
Action: event.Action,
|
||||
ResourceKind: event.ResourceKind,
|
||||
ResourceID: event.ResourceID,
|
||||
Result: event.Result,
|
||||
Summary: event.Summary,
|
||||
CreatedAt: event.CreatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
func TestTableNames(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
User{}.TableName(): "users",
|
||||
AIProvider{}.TableName(): "ai_providers",
|
||||
GamePlugin{}.TableName(): "game_plugins",
|
||||
ServerInstance{}.TableName(): "server_instances",
|
||||
RunEndpoint{}.TableName(): "run_endpoints",
|
||||
Job{}.TableName(): "jobs",
|
||||
Artifact{}.TableName(): "artifacts",
|
||||
LogStream{}.TableName(): "log_streams",
|
||||
AuditEvent{}.TableName(): "audit_events",
|
||||
}
|
||||
|
||||
for got, want := range tests {
|
||||
if got != want {
|
||||
t.Fatalf("expected table name %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
|
||||
source := domain.GamePlugin{
|
||||
ID: "server.scum",
|
||||
Name: "SCUM",
|
||||
Version: "1.0.0",
|
||||
ServerType: "scum",
|
||||
ManifestRef: "artifact://manifest",
|
||||
CreateFormSchemaRef: "artifact://schema",
|
||||
RequiredRunCapabilities: []string{"process.start", "logs.read"},
|
||||
DeclaredPermissions: []string{"server.logs.read"},
|
||||
SupportedOS: []string{"linux"},
|
||||
Pages: []domain.GamePluginPage{
|
||||
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
|
||||
},
|
||||
Tags: []string{"survival"},
|
||||
AIPurposes: []string{"logs.diagnose"},
|
||||
Permissions: domain.PluginPermissions{
|
||||
Jobs: true,
|
||||
Logs: true,
|
||||
},
|
||||
Status: domain.GamePluginStatusInstalled,
|
||||
}
|
||||
|
||||
row := GamePluginFromDomain(source)
|
||||
roundTrip := row.ToDomain()
|
||||
roundTrip.RequiredRunCapabilities[0] = "files.read"
|
||||
roundTrip.DeclaredPermissions[0] = "ai.invoke"
|
||||
roundTrip.SupportedOS[0] = "darwin"
|
||||
roundTrip.Pages[0].Permissions[0] = "ai.invoke"
|
||||
roundTrip.Tags[0] = "mutated"
|
||||
roundTrip.AIPurposes[0] = "config.suggest"
|
||||
|
||||
if source.RequiredRunCapabilities[0] != "process.start" {
|
||||
t.Fatalf("expected source plugin capabilities to remain unchanged, got %+v", source.RequiredRunCapabilities)
|
||||
}
|
||||
if row.RequiredRunCapabilities[0] != "process.start" {
|
||||
t.Fatalf("expected model plugin capabilities to remain unchanged, got %+v", row.RequiredRunCapabilities)
|
||||
}
|
||||
if source.DeclaredPermissions[0] != "server.logs.read" || source.Pages[0].Permissions[0] != "server.logs.read" || source.Tags[0] != "survival" || source.AIPurposes[0] != "logs.diagnose" {
|
||||
t.Fatalf("expected source plugin registry metadata to remain unchanged, got %+v", source)
|
||||
}
|
||||
if row.DeclaredPermissions[0] != "server.logs.read" || row.Pages[0].Permissions[0] != "server.logs.read" || row.Tags[0] != "survival" || row.AIPurposes[0] != "logs.diagnose" {
|
||||
t.Fatalf("expected model plugin registry metadata to remain unchanged, got %+v", row)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIProviderModelUsesKeyReference(t *testing.T) {
|
||||
source := domain.AIProvider{
|
||||
ID: "ai.openai",
|
||||
Name: "OpenAI",
|
||||
Kind: domain.AIProviderKindOpenAI,
|
||||
BaseURL: "https://api.openai.com/v1",
|
||||
APIKeyRef: "secret://providers/openai",
|
||||
Models: []string{"gpt-4.1"},
|
||||
DefaultModel: "gpt-4.1",
|
||||
RelayMode: domain.AIRelayModeDirect,
|
||||
TimeoutMS: 30000,
|
||||
Status: domain.AIProviderStatusActive,
|
||||
RedactionPolicy: "default",
|
||||
}
|
||||
|
||||
row := AIProviderFromDomain(source)
|
||||
if row.APIKeyRef != source.APIKeyRef {
|
||||
t.Fatalf("expected API key reference %q, got %q", source.APIKeyRef, row.APIKeyRef)
|
||||
}
|
||||
|
||||
roundTrip := row.ToDomain()
|
||||
roundTrip.Models[0] = "mutated"
|
||||
if row.Models[0] != "gpt-4.1" {
|
||||
t.Fatalf("expected model provider models to remain unchanged, got %+v", row.Models)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user