功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+6
View File
@@ -13,5 +13,11 @@ Required model groups:
- artifacts and chunks.
- log streams and ingestion cursors.
- audit events.
- durable alerts and their acknowledgement/resolution audit links.
- server-bound plugin lifecycle installations and linked jobs.
- reviewable AI config diffs and approval fences.
Every implemented database model must include field comments, JSON/database tags, and an explicit table name function or equivalent mapping in the chosen stack.
# Durable Client Manager persistence
File and MySQL stores persist the Client Manager installation aggregate, component sessions, registration nonce fences, and lifecycle audit references. Session tokens are stored only as hashes; nonce and heartbeat sequence checks are bounded and restart-safe. Reset, ownership/endpoint reassignment, update activation, rollback, revoke, and uninstall fence or expire component sessions without deleting distribution or audit history.
+94 -10
View File
@@ -181,6 +181,8 @@ type GamePlugin struct {
Tags []string `json:"tags" db:"tags"`
// AIPurposes stores platform-mediated AI usage purposes.
AIPurposes []string `json:"aiPurposes" db:"ai_purposes"`
// ProductionLifecycle stores declared server-bound plugin lifecycle governance.
ProductionLifecycle domain.GamePluginProductionLifecycle `json:"productionLifecycle" db:"production_lifecycle"`
// RemoteAccess stores plugin-declared remote access metadata.
RemoteAccess GamePluginRemoteAccess `json:"remoteAccess" db:"remote_access"`
// RuntimeProfiles stores validated manifest-declared runtime contracts.
@@ -279,14 +281,18 @@ type JobRetryPolicy struct {
}
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"`
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"`
PluginID string `json:"pluginId,omitempty" db:"plugin_id"`
LifecycleOperation string `json:"lifecycleOperation,omitempty" db:"lifecycle_operation"`
TargetVersion string `json:"targetVersion,omitempty" db:"target_version"`
Inputs map[string]string `json:"inputs,omitempty" db:"inputs"`
}
type JobExecutionResult struct {
@@ -428,6 +434,82 @@ type AuditEvent struct {
func (AuditEvent) TableName() string { return "audit_events" }
type Alert struct {
ID string `json:"id" db:"id"`
SourceKind string `json:"sourceKind" db:"source_kind"`
SourceID string `json:"sourceId" db:"source_id"`
RuleKey string `json:"ruleKey" db:"rule_key"`
Severity domain.AlertSeverity `json:"severity" db:"severity"`
State domain.AlertState `json:"state" db:"state"`
Title string `json:"title" db:"title"`
Message string `json:"message" db:"message"`
OccurrenceCount int `json:"occurrenceCount" db:"occurrence_count"`
Retryable bool `json:"retryable" db:"retryable"`
RetryAfterSeconds int `json:"retryAfterSeconds" db:"retry_after_seconds"`
LastJobID string `json:"lastJobId,omitempty" db:"last_job_id"`
LastAuditEventID string `json:"lastAuditEventId,omitempty" db:"last_audit_event_id"`
LastSeenAt time.Time `json:"lastSeenAt" db:"last_seen_at"`
AcknowledgedBy string `json:"acknowledgedBy,omitempty" db:"acknowledged_by"`
AcknowledgedAt time.Time `json:"acknowledgedAt,omitempty" db:"acknowledged_at"`
ResolvedBy string `json:"resolvedBy,omitempty" db:"resolved_by"`
ResolvedAt time.Time `json:"resolvedAt,omitempty" db:"resolved_at"`
ResolutionNote string `json:"resolutionNote,omitempty" db:"resolution_note"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}
func (Alert) TableName() string { return "alerts" }
type PluginLifecycleInstallation struct {
ID string `json:"id" db:"id"`
PluginID string `json:"pluginId" db:"plugin_id"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
CurrentVersion string `json:"currentVersion,omitempty" db:"current_version"`
TargetVersion string `json:"targetVersion,omitempty" db:"target_version"`
PreviousVersion string `json:"previousVersion,omitempty" db:"previous_version"`
DesiredState domain.PluginLifecycleState `json:"desiredState" db:"desired_state"`
CurrentState domain.PluginLifecycleState `json:"currentState" db:"current_state"`
LastOperation domain.PluginLifecycleOperation `json:"lastOperation,omitempty" db:"last_operation"`
Compatibility string `json:"compatibility" db:"compatibility"`
DependencyState domain.DependencyState `json:"dependencyState" db:"dependency_state"`
JobID string `json:"jobId,omitempty" db:"job_id"`
AlertID string `json:"alertId,omitempty" db:"alert_id"`
AuditEventID string `json:"auditEventId,omitempty" db:"audit_event_id"`
FailureReason string `json:"failureReason,omitempty" db:"failure_reason"`
IdempotencyKey string `json:"idempotencyKey" db:"idempotency_key"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}
func (PluginLifecycleInstallation) TableName() string { return "plugin_lifecycle_installations" }
type AIConfigDiff struct {
ID string `json:"id" db:"id"`
RequestID string `json:"requestId" db:"request_id"`
CreatedBy string `json:"createdBy" db:"created_by"`
ServerInstanceID string `json:"serverInstanceId" db:"server_instance_id"`
PluginID string `json:"pluginId,omitempty" db:"plugin_id"`
ProviderID string `json:"providerId,omitempty" db:"provider_id"`
Model string `json:"model,omitempty" db:"model"`
Key string `json:"key" db:"key"`
ConfigVersion int `json:"configVersion" db:"config_version"`
CurrentConfigChecksum string `json:"currentConfigChecksum" db:"current_config_checksum"`
ProposedConfig string `json:"proposedConfig" db:"proposed_config"`
DiffSummary string `json:"diffSummary" db:"diff_summary"`
State domain.AIConfigDiffState `json:"state" db:"state"`
ExpiresAt time.Time `json:"expiresAt" db:"expires_at"`
ApprovedBy string `json:"approvedBy,omitempty" db:"approved_by"`
ApprovedAt time.Time `json:"approvedAt,omitempty" db:"approved_at"`
ApprovalIdempotencyKey string `json:"approvalIdempotencyKey,omitempty" db:"approval_idempotency_key"`
CancelledBy string `json:"cancelledBy,omitempty" db:"cancelled_by"`
CancelledAt time.Time `json:"cancelledAt,omitempty" db:"cancelled_at"`
JobID string `json:"jobId,omitempty" db:"job_id"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}
func (AIConfigDiff) TableName() string { return "ai_config_diffs" }
func UserFromDomain(user domain.User) User {
user = domain.CopyUser(user)
return User{
@@ -511,6 +593,7 @@ func GamePluginFromDomain(plugin domain.GamePlugin) GamePlugin {
Pages: pagesFromDomain(plugin.Pages),
Tags: plugin.Tags,
AIPurposes: plugin.AIPurposes,
ProductionLifecycle: domain.CopyGamePluginProductionLifecycle(plugin.ProductionLifecycle),
RemoteAccess: remoteAccessFromDomain(plugin.RemoteAccess),
RuntimeProfiles: domain.CopyGamePluginRuntimeProfiles(plugin.RuntimeProfiles),
ValidationViolations: plugin.ValidationViolations,
@@ -536,6 +619,7 @@ func (plugin GamePlugin) ToDomain() domain.GamePlugin {
Pages: pagesToDomain(plugin.Pages),
Tags: domain.CopyStringSlice(plugin.Tags),
AIPurposes: domain.CopyStringSlice(plugin.AIPurposes),
ProductionLifecycle: domain.CopyGamePluginProductionLifecycle(plugin.ProductionLifecycle),
RemoteAccess: plugin.RemoteAccess.ToDomain(),
RuntimeProfiles: domain.CopyGamePluginRuntimeProfiles(plugin.RuntimeProfiles),
ValidationViolations: domain.CopyStringSlice(plugin.ValidationViolations),
@@ -798,11 +882,11 @@ func (job Job) ToDomain() domain.Job {
}
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}
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, PluginID: input.PluginID, LifecycleOperation: input.LifecycleOperation, TargetVersion: input.TargetVersion, Inputs: domain.CopyStringMap(input.Inputs)}
}
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}
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, PluginID: input.PluginID, LifecycleOperation: input.LifecycleOperation, TargetVersion: input.TargetVersion, Inputs: domain.CopyStringMap(input.Inputs)}
}
func executionResultFromDomain(result domain.JobExecutionResult) JobExecutionResult {
+47 -2
View File
@@ -1,6 +1,7 @@
package model
import (
"reflect"
"testing"
"browser.local/platform/domain"
@@ -17,6 +18,9 @@ func TestTableNames(t *testing.T) {
Artifact{}.TableName(): "artifacts",
LogStream{}.TableName(): "log_streams",
AuditEvent{}.TableName(): "audit_events",
Alert{}.TableName(): "alerts",
PluginLifecycleInstallation{}.TableName(): "plugin_lifecycle_installations",
AIConfigDiff{}.TableName(): "ai_config_diffs",
ClientManagerInstallation{}.TableName(): "client_manager_installations",
ClientManagerSession{}.TableName(): "client_manager_sessions",
ClientManagerRegistrationNonce{}.TableName(): "client_manager_registration_nonces",
@@ -43,8 +47,13 @@ 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"},
ProductionLifecycle: domain.GamePluginProductionLifecycle{
Operations: []string{"install", "upgrade", "rollback"},
DependencyPolicy: "required",
ApprovalRequired: []string{"rollback"},
},
RuntimeProfiles: domain.GamePluginRuntimeProfiles{LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}}},
Permissions: domain.PluginPermissions{
Jobs: true,
@@ -61,6 +70,8 @@ func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) {
roundTrip.Pages[0].Permissions[0] = "ai.invoke"
roundTrip.Tags[0] = "mutated"
roundTrip.AIPurposes[0] = "config.suggest"
roundTrip.ProductionLifecycle.Operations[0] = "retire"
roundTrip.ProductionLifecycle.ApprovalRequired[0] = "disable"
roundTrip.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] = "process.stop"
if source.RequiredRunCapabilities[0] != "process.start" {
@@ -75,11 +86,45 @@ 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.ProductionLifecycle.Operations[0] != "install" || row.ProductionLifecycle.Operations[0] != "install" || source.ProductionLifecycle.ApprovalRequired[0] != "rollback" || row.ProductionLifecycle.ApprovalRequired[0] != "rollback" {
t.Fatalf("expected production lifecycle declaration to round-trip without aliasing, source=%+v row=%+v", source.ProductionLifecycle, row.ProductionLifecycle)
}
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 TestJobExecutionInputModelRoundTripPreservesLifecycleMetadata(t *testing.T) {
source := domain.JobExecutionInput{
WorkspaceScope: "server-workspace",
PluginID: "game.scum",
LifecycleOperation: "upgrade",
TargetVersion: "2.0.0",
Inputs: map[string]string{"templateKey": "players.by-id", "playerId": "steam-123"},
}
row := executionInputFromDomain(source)
source.Inputs["playerId"] = "source-mutated"
if row.Inputs["playerId"] != "steam-123" {
t.Fatalf("expected model execution inputs to be isolated from source mutation, row=%+v", row.Inputs)
}
source.Inputs["playerId"] = "steam-123"
row.Inputs["playerId"] = "row-mutated"
if source.Inputs["playerId"] != "steam-123" {
t.Fatalf("expected source execution inputs to be isolated from model mutation, source=%+v", source.Inputs)
}
row.Inputs["playerId"] = "steam-123"
roundTrip := row.ToDomain()
if !reflect.DeepEqual(roundTrip, source) {
t.Fatalf("expected lifecycle execution metadata to round-trip, got %+v", roundTrip)
}
roundTrip.Inputs["playerId"] = "mutated"
if source.Inputs["playerId"] != "steam-123" || row.Inputs["playerId"] != "steam-123" {
t.Fatalf("expected execution inputs to round-trip without aliasing, source=%+v row=%+v", source.Inputs, row.Inputs)
}
}
func TestAIProviderModelUsesKeyReference(t *testing.T) {
source := domain.AIProvider{
ID: "ai.openai",