feat: 完整游戏运维功能
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user