feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
+114
View File
@@ -0,0 +1,114 @@
package model
import (
"time"
"browser.local/platform/domain"
)
// ClientManagerInstallation is the durable desired/observed lifecycle aggregate for one server/profile.
type ClientManagerInstallation struct {
// ID is the stable installation identifier.
ID string `json:"id" db:"id"`
// ServerInstanceID scopes the installation to one authorized server.
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
// PluginID identifies the installed game plugin declaration.
PluginID string `json:"pluginId" db:"plugin_id"`
// ProfileKey identifies the plugin client-manager profile.
ProfileKey string `json:"profileKey" db:"profile_key"`
// RunEndpointID is the fenced machine executor assignment.
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
// TargetOS is the package operating-system target.
TargetOS string `json:"targetOs" db:"target_os"`
// TargetArch is the package architecture target.
TargetArch string `json:"targetArch" db:"target_arch"`
// Status is the durable lifecycle state.
Status domain.ClientManagerLifecycleStatus `json:"status" db:"status"`
// Phase is a bounded safe execution phase.
Phase string `json:"phase" db:"phase"`
DesiredVersion string `json:"desiredVersion" db:"desired_version"`
ActiveVersion string `json:"activeVersion" db:"active_version"`
PreviousVersion string `json:"previousVersion" db:"previous_version"`
DesiredRevision string `json:"desiredRevision" db:"desired_revision"`
ActiveRevision string `json:"activeRevision" db:"active_revision"`
PreviousRevision string `json:"previousRevision" db:"previous_revision"`
DesiredArtifactID string `json:"desiredArtifactId" db:"desired_artifact_id"`
ActiveArtifactID string `json:"activeArtifactId" db:"active_artifact_id"`
PreviousArtifactID string `json:"previousArtifactId" db:"previous_artifact_id"`
Checksum string `json:"checksum" db:"checksum"`
KeyGeneration int `json:"keyGeneration" db:"key_generation"`
DeploymentGeneration int `json:"deploymentGeneration" db:"deployment_generation"`
CurrentJobID string `json:"currentJobId" db:"current_job_id"`
LastSuccessfulJobID string `json:"lastSuccessfulJobId" db:"last_successful_job_id"`
LastOperation domain.ClientManagerLifecycleOperation `json:"lastOperation" db:"last_operation"`
Health domain.ClientManagerHealthStatus `json:"health" db:"health"`
HealthReason string `json:"healthReason" db:"health_reason"`
LastSeenAt time.Time `json:"lastSeenAt" db:"last_seen_at"`
LastHeartbeatSequence uint64 `json:"lastHeartbeatSequence" db:"last_heartbeat_sequence"`
Retryable bool `json:"retryable" db:"retryable"`
RequiresRedeploy bool `json:"requiresRedeploy" db:"requires_redeploy"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
InstalledAt time.Time `json:"installedAt" db:"installed_at"`
UninstalledAt time.Time `json:"uninstalledAt" db:"uninstalled_at"`
}
func (ClientManagerInstallation) TableName() string { return "client_manager_installations" }
// ClientManagerSession stores only the hash and fences for a short-lived component session.
type ClientManagerSession struct {
ID string `json:"id" db:"id"`
InstallationID string `json:"installationId" db:"installation_id"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
ProfileKey string `json:"profileKey" db:"profile_key"`
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
ArtifactID string `json:"artifactId" db:"artifact_id"`
KeyGeneration int `json:"keyGeneration" db:"key_generation"`
DeploymentGeneration int `json:"deploymentGeneration" db:"deployment_generation"`
TokenHash string `json:"tokenHash" db:"token_hash"`
Capabilities []string `json:"capabilities" db:"capabilities"`
Status domain.ClientManagerSessionStatus `json:"status" db:"status"`
LastHeartbeatSequence uint64 `json:"lastHeartbeatSequence" db:"last_heartbeat_sequence"`
LastSeenAt time.Time `json:"lastSeenAt" db:"last_seen_at"`
ExpiresAt time.Time `json:"expiresAt" db:"expires_at"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
RevokedAt time.Time `json:"revokedAt" db:"revoked_at"`
}
func (ClientManagerSession) TableName() string { return "client_manager_sessions" }
// ClientManagerRegistrationNonce is a bounded replay fence for one signed registration request.
type ClientManagerRegistrationNonce struct {
ID string `json:"id" db:"id"`
InstallationID string `json:"installationId" db:"installation_id"`
ExpiresAt time.Time `json:"expiresAt" db:"expires_at"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
}
func (ClientManagerRegistrationNonce) TableName() string { return "client_manager_registration_nonces" }
func ClientManagerInstallationFromDomain(value domain.ClientManagerInstallation) ClientManagerInstallation {
return ClientManagerInstallation(value)
}
func (value ClientManagerInstallation) ToDomain() domain.ClientManagerInstallation {
return domain.ClientManagerInstallation(value)
}
func ClientManagerSessionFromDomain(value domain.ClientManagerSession) ClientManagerSession {
value = domain.CopyClientManagerSession(value)
return ClientManagerSession(value)
}
func (value ClientManagerSession) ToDomain() domain.ClientManagerSession {
return domain.CopyClientManagerSession(domain.ClientManagerSession(value))
}
func ClientManagerRegistrationNonceFromDomain(value domain.ClientManagerRegistrationNonce) ClientManagerRegistrationNonce {
return ClientManagerRegistrationNonce(value)
}
func (value ClientManagerRegistrationNonce) ToDomain() domain.ClientManagerRegistrationNonce {
return domain.ClientManagerRegistrationNonce(value)
}
+16
View File
@@ -10,6 +10,7 @@ type RuntimeBinding struct {
ID string `json:"id" db:"id"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
PluginID string `json:"pluginId" db:"plugin_id"`
PluginVersion string `json:"pluginVersion" db:"plugin_version"`
ProfileKey string `json:"profileKey" db:"profile_key"`
Mode string `json:"mode" db:"mode"`
Bindings map[string]string `json:"bindings" db:"bindings"`
@@ -64,6 +65,7 @@ type ClientManagerDistribution struct {
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
PluginID string `json:"pluginId" db:"plugin_id"`
ProfileKey string `json:"profileKey" db:"profile_key"`
Version string `json:"version" db:"version"`
TargetOS string `json:"targetOs" db:"target_os"`
TargetArch string `json:"targetArch" db:"target_arch"`
RepositoryURL string `json:"repositoryUrl" db:"repository_url"`
@@ -90,6 +92,10 @@ type DependencyStatus struct {
State domain.DependencyState `json:"state" db:"state"`
Required bool `json:"required" db:"required"`
InstallPlanKey string `json:"installPlanKey,omitempty" db:"install_plan_key"`
PlanDigest string `json:"planDigest,omitempty" db:"plan_digest"`
JobID string `json:"jobId,omitempty" db:"job_id"`
Evidence string `json:"evidence,omitempty" db:"evidence"`
CompletedSteps int `json:"completedSteps,omitempty" db:"completed_steps"`
Message string `json:"message,omitempty" db:"message"`
CheckedAt time.Time `json:"checkedAt" db:"checked_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
@@ -102,6 +108,7 @@ type ClientManagerBuildJob struct {
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
PluginID string `json:"pluginId" db:"plugin_id"`
ProfileKey string `json:"profileKey" db:"profile_key"`
Version string `json:"version" db:"version"`
TargetOS string `json:"targetOs" db:"target_os"`
TargetArch string `json:"targetArch" db:"target_arch"`
RepositoryURL string `json:"repositoryUrl" db:"repository_url"`
@@ -123,9 +130,16 @@ type RunUpdateJob struct {
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
ArtifactID string `json:"artifactId" db:"artifact_id"`
Checksum string `json:"checksum" db:"checksum"`
TargetOS string `json:"targetOs" db:"target_os"`
TargetArch string `json:"targetArch" db:"target_arch"`
TargetRelease string `json:"targetRelease,omitempty" db:"target_release"`
PreviousVersion string `json:"previousVersion,omitempty" db:"previous_version"`
JobID string `json:"jobId" db:"job_id"`
IdempotencyKey string `json:"idempotencyKey" db:"idempotency_key"`
Status domain.DistributionJobStatus `json:"status" db:"status"`
Phase domain.RunUpdatePhase `json:"phase" db:"phase"`
Message string `json:"message,omitempty" db:"message"`
Rollback bool `json:"rollback,omitempty" db:"rollback"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}
@@ -138,6 +152,7 @@ func RuntimeBindingFromDomain(binding domain.RuntimeBinding) RuntimeBinding {
ID: binding.ID,
ServerInstanceID: binding.ServerInstanceID,
PluginID: binding.PluginID,
PluginVersion: binding.PluginVersion,
ProfileKey: binding.ProfileKey,
Mode: binding.Mode,
Bindings: binding.Bindings,
@@ -153,6 +168,7 @@ func (binding RuntimeBinding) ToDomain() domain.RuntimeBinding {
ID: binding.ID,
ServerInstanceID: binding.ServerInstanceID,
PluginID: binding.PluginID,
PluginVersion: binding.PluginVersion,
ProfileKey: binding.ProfileKey,
Mode: binding.Mode,
Bindings: domain.CopyStringMap(binding.Bindings),
+56
View File
@@ -0,0 +1,56 @@
package model
import (
"time"
"browser.local/platform/domain"
)
type MetricSample struct {
ID string `json:"id" db:"id"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
Online bool `json:"online" db:"online"`
PlayerCount *int `json:"playerCount,omitempty" db:"player_count"`
MaxPlayers *int `json:"maxPlayers,omitempty" db:"max_players"`
TPS *float64 `json:"tps,omitempty" db:"tps"`
LatencyMS *float64 `json:"latencyMs,omitempty" db:"latency_ms"`
CPUPercent *float64 `json:"cpuPercent,omitempty" db:"cpu_percent"`
MemoryPercent *float64 `json:"memoryPercent,omitempty" db:"memory_percent"`
DiskPercent *float64 `json:"diskPercent,omitempty" db:"disk_percent"`
Source string `json:"source" db:"source"`
CollectedAt time.Time `json:"collectedAt" db:"collected_at"`
}
func (MetricSample) TableName() string { return "metric_samples" }
func MetricSampleFromDomain(sample domain.MetricSample) MetricSample {
return MetricSample{ID: sample.ID, ServerInstanceID: sample.ServerInstanceID, RunEndpointID: sample.RunEndpointID, Online: sample.Online, PlayerCount: sample.PlayerCount, MaxPlayers: sample.MaxPlayers, TPS: sample.TPS, LatencyMS: sample.LatencyMS, CPUPercent: sample.CPUPercent, MemoryPercent: sample.MemoryPercent, DiskPercent: sample.DiskPercent, Source: sample.Source, CollectedAt: sample.CollectedAt}
}
func (sample MetricSample) ToDomain() domain.MetricSample {
return domain.MetricSample{ID: sample.ID, ServerInstanceID: sample.ServerInstanceID, RunEndpointID: sample.RunEndpointID, Online: sample.Online, PlayerCount: sample.PlayerCount, MaxPlayers: sample.MaxPlayers, TPS: sample.TPS, LatencyMS: sample.LatencyMS, CPUPercent: sample.CPUPercent, MemoryPercent: sample.MemoryPercent, DiskPercent: sample.DiskPercent, Source: sample.Source, CollectedAt: sample.CollectedAt}
}
type BackupRecord struct {
ID string `json:"id" db:"id"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
ArtifactID string `json:"artifactId" db:"artifact_id"`
Checksum string `json:"checksum" db:"checksum"`
SizeBytes int64 `json:"sizeBytes" db:"size_bytes"`
State domain.BackupState `json:"state" db:"state"`
RecoveryStatus string `json:"recoveryStatus" db:"recovery_status"`
RetentionUntil time.Time `json:"retentionUntil,omitempty" db:"retention_until"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}
func (BackupRecord) TableName() string { return "backup_records" }
func BackupRecordFromDomain(record domain.BackupRecord) BackupRecord {
return BackupRecord{ID: record.ID, ServerInstanceID: record.ServerInstanceID, ArtifactID: record.ArtifactID, Checksum: record.Checksum, SizeBytes: record.SizeBytes, State: record.State, RecoveryStatus: record.RecoveryStatus, RetentionUntil: record.RetentionUntil, CreatedAt: record.CreatedAt, UpdatedAt: record.UpdatedAt}
}
func (record BackupRecord) ToDomain() domain.BackupRecord {
return domain.BackupRecord{ID: record.ID, ServerInstanceID: record.ServerInstanceID, ArtifactID: record.ArtifactID, Checksum: record.Checksum, SizeBytes: record.SizeBytes, State: record.State, RecoveryStatus: record.RecoveryStatus, RetentionUntil: record.RetentionUntil, CreatedAt: record.CreatedAt, UpdatedAt: record.UpdatedAt}
}
+247 -42
View File
@@ -31,6 +31,44 @@ type User struct {
func (User) TableName() string { return "users" }
type AuthSession struct {
// ID is a non-secret stable session record identifier.
ID string `json:"id" db:"id"`
// UserID owns the authenticated session.
UserID string `json:"userId" db:"user_id"`
// TokenHash is a one-way verifier; the bearer token is never persisted.
TokenHash string `json:"tokenHash" db:"token_hash"`
// Status tracks active or revoked lifecycle state.
Status domain.AuthSessionStatus `json:"status" db:"status"`
// Generation increments when a user rotates a session.
Generation int `json:"generation" db:"generation"`
IssuedAt time.Time `json:"issuedAt" db:"issued_at"`
ExpiresAt time.Time `json:"expiresAt" db:"expires_at"`
LastSeenAt time.Time `json:"lastSeenAt" db:"last_seen_at"`
RevokedAt time.Time `json:"revokedAt,omitempty" db:"revoked_at"`
}
func (AuthSession) TableName() string { return "auth_sessions" }
type RunControlSession struct {
// RunEndpointID is both the endpoint owner and stable session record ID.
RunEndpointID string `json:"runEndpointId" db:"run_endpoint_id"`
// SessionTokenHash is a one-way verifier; raw Run tokens are never persisted.
SessionTokenHash string `json:"sessionTokenHash" db:"session_token_hash"`
Status domain.AuthSessionStatus `json:"status" db:"status"`
Generation int `json:"generation" db:"generation"`
CapabilityFingerprint string `json:"capabilityFingerprint" db:"capability_fingerprint"`
HeartbeatIntervalSeconds int `json:"heartbeatIntervalSeconds" db:"heartbeat_interval_seconds"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
ExpiresAt time.Time `json:"expiresAt" db:"expires_at"`
RevokedAt time.Time `json:"revokedAt,omitempty" db:"revoked_at"`
RequireSignedRequests bool `json:"requireSignedRequests" db:"require_signed_requests"`
UsedNonces []string `json:"usedNonces,omitempty" db:"used_nonces"`
}
func (RunControlSession) TableName() string { return "run_control_sessions" }
type AIProvider struct {
// ID is the stable AI provider identifier.
ID string `json:"id" db:"id"`
@@ -145,6 +183,8 @@ type GamePlugin struct {
AIPurposes []string `json:"aiPurposes" db:"ai_purposes"`
// RemoteAccess stores plugin-declared remote access metadata.
RemoteAccess GamePluginRemoteAccess `json:"remoteAccess" db:"remote_access"`
// RuntimeProfiles stores validated manifest-declared runtime contracts.
RuntimeProfiles domain.GamePluginRuntimeProfiles `json:"runtimeProfiles" db:"runtime_profiles"`
// ValidationViolations stores safe validation findings for invalid plugins.
ValidationViolations []string `json:"validationViolations" db:"validation_violations"`
// Status is the plugin lifecycle status.
@@ -172,6 +212,14 @@ type ServerInstance struct {
State domain.ServerInstanceState `json:"state" db:"state"`
// ConfigVersion is the platform-managed optimistic concurrency version.
ConfigVersion int `json:"configVersion" db:"config_version"`
// ConfigKey is the logical configuration target, never a host path.
ConfigKey string `json:"configKey,omitempty" db:"config_key"`
// ConfigContent is the last platform-approved bounded configuration body.
ConfigContent string `json:"configContent,omitempty" db:"config_content"`
// ConfigChecksum is the SHA-256 checksum of ConfigContent.
ConfigChecksum string `json:"configChecksum,omitempty" db:"config_checksum"`
// ConfigUpdatedAt records the last accepted config execution result.
ConfigUpdatedAt time.Time `json:"configUpdatedAt,omitempty" db:"config_updated_at"`
// CreatedAt is the record creation timestamp.
CreatedAt time.Time `json:"createdAt" db:"created_at"`
// UpdatedAt is the last update timestamp.
@@ -198,6 +246,10 @@ type RunEndpoint struct {
DisplayName string `json:"displayName" db:"display_name"`
// Version is the run binary version.
Version string `json:"version" db:"version"`
// Platform is the endpoint operating system.
Platform string `json:"platform" db:"platform"`
// Architecture is the endpoint CPU architecture.
Architecture string `json:"architecture" db:"architecture"`
// Status is the current endpoint status.
Status domain.RunEndpointStatus `json:"status" db:"status"`
// Capabilities lists advertised run capability keys.
@@ -217,6 +269,38 @@ type JobProgress struct {
Message string `json:"message,omitempty" db:"message"`
}
type JobRetryPolicy struct {
// MaxAttempts bounds total claims, including the first attempt.
MaxAttempts int `json:"maxAttempts" db:"max_attempts"`
// InitialBackoffSeconds is the first retry delay.
InitialBackoffSeconds int `json:"initialBackoffSeconds" db:"initial_backoff_seconds"`
// MaxBackoffSeconds caps exponential retry delay.
MaxBackoffSeconds int `json:"maxBackoffSeconds" db:"max_backoff_seconds"`
}
type JobExecutionInput struct {
WorkspaceScope string `json:"workspaceScope,omitempty" db:"workspace_scope"`
Content string `json:"content,omitempty" db:"content"`
ExpectedVersion int `json:"expectedVersion,omitempty" db:"expected_version"`
ExpectedChecksum string `json:"expectedChecksum,omitempty" db:"expected_checksum"`
MaxReadBytes int `json:"maxReadBytes,omitempty" db:"max_read_bytes"`
RemoteAdapterKey string `json:"remoteAdapterKey,omitempty" db:"remote_adapter_key"`
RemoteAdapterKind string `json:"remoteAdapterKind,omitempty" db:"remote_adapter_kind"`
TimeoutSeconds int `json:"timeoutSeconds,omitempty" db:"timeout_seconds"`
}
type JobExecutionResult struct {
Kind string `json:"kind,omitempty" db:"kind"`
ProcessState string `json:"processState,omitempty" db:"process_state"`
ExitClassification string `json:"exitClassification,omitempty" db:"exit_classification"`
ExitCode int `json:"exitCode,omitempty" db:"exit_code"`
Version int `json:"version,omitempty" db:"version"`
Checksum string `json:"checksum,omitempty" db:"checksum"`
SizeBytes int64 `json:"sizeBytes,omitempty" db:"size_bytes"`
AuditSummary string `json:"auditSummary,omitempty" db:"audit_summary"`
Content string `json:"content,omitempty" db:"content"`
}
type Job struct {
// ID is the stable job identifier.
ID string `json:"id" db:"id"`
@@ -238,6 +322,39 @@ type Job struct {
Progress JobProgress `json:"progress" db:"progress"`
// ResultRef references the terminal result artifact or summary.
ResultRef string `json:"resultRef,omitempty" db:"result_ref"`
// ExecutionInput is private approved input delivered only to fenced Run assignments.
ExecutionInput JobExecutionInput `json:"executionInput,omitempty" db:"execution_input"`
// ExecutionResult stores typed execution evidence; private content is not user-projected.
ExecutionResult JobExecutionResult `json:"executionResult,omitempty" db:"execution_result"`
// RetryPolicy stores bounded durable retry settings.
RetryPolicy JobRetryPolicy `json:"retryPolicy" db:"retry_policy"`
// Attempt is the current monotonic per-job attempt.
Attempt int `json:"attempt" db:"attempt"`
// QueueEligibleAt is the first time queued work may be claimed.
QueueEligibleAt time.Time `json:"queueEligibleAt,omitempty" db:"queue_eligible_at"`
// NextAttemptAt is the durable retry eligibility time.
NextAttemptAt time.Time `json:"nextAttemptAt,omitempty" db:"next_attempt_at"`
// LeaseTokenHash stores a one-way verifier, never the raw lease token.
LeaseTokenHash string `json:"leaseTokenHash,omitempty" db:"lease_token_hash"`
// LeaseSessionGen fences the attempt to an authenticated Run session generation.
LeaseSessionGen int `json:"leaseSessionGeneration,omitempty" db:"lease_session_generation"`
// AckDeadlineAt bounds how long Run has to acknowledge a claim.
AckDeadlineAt time.Time `json:"ackDeadlineAt,omitempty" db:"ack_deadline_at"`
// LeaseExpiresAt bounds execution without progress or reconciliation.
LeaseExpiresAt time.Time `json:"leaseExpiresAt,omitempty" db:"lease_expires_at"`
// LastProgressSeq rejects reordered progress updates.
LastProgressSeq uint64 `json:"lastProgressSequence,omitempty" db:"last_progress_sequence"`
// CancelReason and timestamps persist cancellation intent and result.
CancelReason string `json:"cancelReason,omitempty" db:"cancel_reason"`
CancelRequestedAt time.Time `json:"cancelRequestedAt,omitempty" db:"cancel_requested_at"`
CancelCompletedAt time.Time `json:"cancelCompletedAt,omitempty" db:"cancel_completed_at"`
// TerminalAt and TerminalFingerprint make terminal replay durable and idempotent.
TerminalAt time.Time `json:"terminalAt,omitempty" db:"terminal_at"`
TerminalFingerprint string `json:"terminalFingerprint,omitempty" db:"terminal_fingerprint"`
// Reconciliation fields provide restart recovery evidence.
LastReconciledAt time.Time `json:"lastReconciledAt,omitempty" db:"last_reconciled_at"`
ReconcileCount int `json:"reconcileCount,omitempty" db:"reconcile_count"`
ReconcileOutcome string `json:"reconcileOutcome,omitempty" db:"reconcile_outcome"`
// CreatedAt is the record creation timestamp.
CreatedAt time.Time `json:"createdAt" db:"created_at"`
// UpdatedAt is the last update timestamp.
@@ -395,6 +512,7 @@ func GamePluginFromDomain(plugin domain.GamePlugin) GamePlugin {
Tags: plugin.Tags,
AIPurposes: plugin.AIPurposes,
RemoteAccess: remoteAccessFromDomain(plugin.RemoteAccess),
RuntimeProfiles: domain.CopyGamePluginRuntimeProfiles(plugin.RuntimeProfiles),
ValidationViolations: plugin.ValidationViolations,
Status: plugin.Status,
}
@@ -419,6 +537,7 @@ func (plugin GamePlugin) ToDomain() domain.GamePlugin {
Tags: domain.CopyStringSlice(plugin.Tags),
AIPurposes: domain.CopyStringSlice(plugin.AIPurposes),
RemoteAccess: plugin.RemoteAccess.ToDomain(),
RuntimeProfiles: domain.CopyGamePluginRuntimeProfiles(plugin.RuntimeProfiles),
ValidationViolations: domain.CopyStringSlice(plugin.ValidationViolations),
Status: plugin.Status,
}
@@ -521,29 +640,41 @@ func remoteAccessFromDomain(remote domain.GamePluginRemoteAccess) GamePluginRemo
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,
ID: instance.ID,
PluginID: instance.PluginID,
PluginVersion: instance.PluginVersion,
RunEndpointID: instance.RunEndpointID,
Name: instance.Name,
OwnerUserID: instance.OwnerUserID,
AdminUserIDs: domain.CopyStringSlice(instance.AdminUserIDs),
State: instance.State,
ConfigVersion: instance.ConfigVersion,
ConfigKey: instance.ConfigKey,
ConfigContent: instance.ConfigContent,
ConfigChecksum: instance.ConfigChecksum,
ConfigUpdatedAt: instance.ConfigUpdatedAt,
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,
ID: instance.ID,
PluginID: instance.PluginID,
PluginVersion: instance.PluginVersion,
RunEndpointID: instance.RunEndpointID,
Name: instance.Name,
OwnerUserID: instance.OwnerUserID,
AdminUserIDs: domain.CopyStringSlice(instance.AdminUserIDs),
State: instance.State,
ConfigVersion: instance.ConfigVersion,
ConfigKey: instance.ConfigKey,
ConfigContent: instance.ConfigContent,
ConfigChecksum: instance.ConfigChecksum,
ConfigUpdatedAt: instance.ConfigUpdatedAt,
CreatedAt: instance.CreatedAt,
UpdatedAt: instance.UpdatedAt,
}
}
@@ -553,6 +684,8 @@ func RunEndpointFromDomain(endpoint domain.RunEndpoint) RunEndpoint {
ID: endpoint.ID,
DisplayName: endpoint.DisplayName,
Version: endpoint.Version,
Platform: endpoint.Platform,
Architecture: endpoint.Architecture,
Status: endpoint.Status,
Capabilities: endpoint.Capabilities,
Capacity: capacityFromDomain(endpoint.Capacity),
@@ -565,6 +698,8 @@ func (endpoint RunEndpoint) ToDomain() domain.RunEndpoint {
ID: endpoint.ID,
DisplayName: endpoint.DisplayName,
Version: endpoint.Version,
Platform: endpoint.Platform,
Architecture: endpoint.Architecture,
Status: endpoint.Status,
Capabilities: domain.CopyStringSlice(endpoint.Capabilities),
Capacity: endpoint.Capacity.ToDomain(),
@@ -592,35 +727,105 @@ func capacityFromDomain(capacity domain.RunCapacity) RunCapacity {
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,
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,
ExecutionInput: executionInputFromDomain(job.ExecutionInput),
ExecutionResult: executionResultFromDomain(job.ExecutionResult),
RetryPolicy: retryPolicyFromDomain(job.RetryPolicy),
Attempt: job.Attempt,
QueueEligibleAt: job.QueueEligibleAt,
NextAttemptAt: job.NextAttemptAt,
LeaseTokenHash: job.LeaseTokenHash,
LeaseSessionGen: job.LeaseSessionGen,
AckDeadlineAt: job.AckDeadlineAt,
LeaseExpiresAt: job.LeaseExpiresAt,
LastProgressSeq: job.LastProgressSeq,
CancelReason: job.CancelReason,
CancelRequestedAt: job.CancelRequestedAt,
CancelCompletedAt: job.CancelCompletedAt,
TerminalAt: job.TerminalAt,
TerminalFingerprint: job.TerminalFingerprint,
LastReconciledAt: job.LastReconciledAt,
ReconcileCount: job.ReconcileCount,
ReconcileOutcome: job.ReconcileOutcome,
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,
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,
ExecutionInput: job.ExecutionInput.ToDomain(),
ExecutionResult: job.ExecutionResult.ToDomain(),
RetryPolicy: job.RetryPolicy.ToDomain(),
Attempt: job.Attempt,
QueueEligibleAt: job.QueueEligibleAt,
NextAttemptAt: job.NextAttemptAt,
LeaseTokenHash: job.LeaseTokenHash,
LeaseSessionGen: job.LeaseSessionGen,
AckDeadlineAt: job.AckDeadlineAt,
LeaseExpiresAt: job.LeaseExpiresAt,
LastProgressSeq: job.LastProgressSeq,
CancelReason: job.CancelReason,
CancelRequestedAt: job.CancelRequestedAt,
CancelCompletedAt: job.CancelCompletedAt,
TerminalAt: job.TerminalAt,
TerminalFingerprint: job.TerminalFingerprint,
LastReconciledAt: job.LastReconciledAt,
ReconcileCount: job.ReconcileCount,
ReconcileOutcome: job.ReconcileOutcome,
CreatedAt: job.CreatedAt,
UpdatedAt: job.UpdatedAt,
}
}
func executionInputFromDomain(input domain.JobExecutionInput) JobExecutionInput {
return JobExecutionInput{WorkspaceScope: input.WorkspaceScope, Content: input.Content, ExpectedVersion: input.ExpectedVersion, ExpectedChecksum: input.ExpectedChecksum, MaxReadBytes: input.MaxReadBytes, RemoteAdapterKey: input.RemoteAdapterKey, RemoteAdapterKind: input.RemoteAdapterKind, TimeoutSeconds: input.TimeoutSeconds}
}
func (input JobExecutionInput) ToDomain() domain.JobExecutionInput {
return domain.JobExecutionInput{WorkspaceScope: input.WorkspaceScope, Content: input.Content, ExpectedVersion: input.ExpectedVersion, ExpectedChecksum: input.ExpectedChecksum, MaxReadBytes: input.MaxReadBytes, RemoteAdapterKey: input.RemoteAdapterKey, RemoteAdapterKind: input.RemoteAdapterKind, TimeoutSeconds: input.TimeoutSeconds}
}
func executionResultFromDomain(result domain.JobExecutionResult) JobExecutionResult {
return JobExecutionResult{Kind: result.Kind, ProcessState: result.ProcessState, ExitClassification: result.ExitClassification, ExitCode: result.ExitCode, Version: result.Version, Checksum: result.Checksum, SizeBytes: result.SizeBytes, AuditSummary: result.AuditSummary, Content: result.Content}
}
func (result JobExecutionResult) ToDomain() domain.JobExecutionResult {
return domain.JobExecutionResult{Kind: result.Kind, ProcessState: result.ProcessState, ExitClassification: result.ExitClassification, ExitCode: result.ExitCode, Version: result.Version, Checksum: result.Checksum, SizeBytes: result.SizeBytes, AuditSummary: result.AuditSummary, Content: result.Content}
}
func (policy JobRetryPolicy) ToDomain() domain.JobRetryPolicy {
return domain.JobRetryPolicy{
MaxAttempts: policy.MaxAttempts,
InitialBackoffSeconds: policy.InitialBackoffSeconds,
MaxBackoffSeconds: policy.MaxBackoffSeconds,
}
}
func retryPolicyFromDomain(policy domain.JobRetryPolicy) JobRetryPolicy {
return JobRetryPolicy{
MaxAttempts: policy.MaxAttempts,
InitialBackoffSeconds: policy.InitialBackoffSeconds,
MaxBackoffSeconds: policy.MaxBackoffSeconds,
}
}
+19 -11
View File
@@ -8,15 +8,18 @@ import (
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",
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",
ClientManagerInstallation{}.TableName(): "client_manager_installations",
ClientManagerSession{}.TableName(): "client_manager_sessions",
ClientManagerRegistrationNonce{}.TableName(): "client_manager_registration_nonces",
}
for got, want := range tests {
@@ -40,8 +43,9 @@ func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
Pages: []domain.GamePluginPage{
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
},
Tags: []string{"survival"},
AIPurposes: []string{"logs.diagnose"},
Tags: []string{"survival"},
AIPurposes: []string{"logs.diagnose"},
RuntimeProfiles: domain.GamePluginRuntimeProfiles{LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}}},
Permissions: domain.PluginPermissions{
Jobs: true,
Logs: true,
@@ -57,6 +61,7 @@ func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
roundTrip.Pages[0].Permissions[0] = "ai.invoke"
roundTrip.Tags[0] = "mutated"
roundTrip.AIPurposes[0] = "config.suggest"
roundTrip.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] = "process.stop"
if source.RequiredRunCapabilities[0] != "process.start" {
t.Fatalf("expected source plugin capabilities to remain unchanged, got %+v", source.RequiredRunCapabilities)
@@ -70,6 +75,9 @@ func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
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)
}
if source.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" || row.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" {
t.Fatalf("expected runtime profiles to round-trip without aliasing, source=%+v row=%+v", source.RuntimeProfiles, row.RuntimeProfiles)
}
}
func TestAIProviderModelUsesKeyReference(t *testing.T) {