Declare SCUM Run data targets
This commit is contained in:
@@ -20,21 +20,25 @@ func TestSCUMSchemaProbeEndpointQueuesPlatformScheduledDurableJob(t *testing.T)
|
||||
ServerType: "scum",
|
||||
ManifestRef: "artifact://manifests/server.scum/1.0.0",
|
||||
CreateFormSchemaRef: "artifact://schemas/server.scum/create-form/1.0.0",
|
||||
RequiredRunCapabilities: []string{"process.start", domain.JobCapabilityRemoteRunDBSQLiteProbe},
|
||||
RequiredRunCapabilities: []string{"process.start", domain.JobCapabilityRemoteRunFilesRead, domain.JobCapabilityRemoteRunDBSQLiteProbe},
|
||||
DeclaredPermissions: []string{"server.remote.access"},
|
||||
Permissions: domain.PluginPermissions{Jobs: true, RemoteAccess: true},
|
||||
LifecycleActions: domain.PluginLifecycleActions{Start: "actions/start.json"},
|
||||
RemoteAccess: domain.GamePluginRemoteAccess{Methods: []string{"run"}, RunCapabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
|
||||
RuntimeProfiles: domain.GamePluginRuntimeProfiles{
|
||||
LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}},
|
||||
TransportProfiles: []domain.RuntimeTransportProfile{{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}}},
|
||||
TransportProfiles: []domain.RuntimeTransportProfile{
|
||||
{Key: "server-files", Kind: "file", TargetKey: "server-root", Capabilities: []string{domain.JobCapabilityRemoteRunFilesRead}},
|
||||
{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
|
||||
},
|
||||
DataTargets: []domain.RuntimeDataTarget{{Key: "scum-database", Kind: "sqlite.snapshot", TransportKey: "scum-database", SourceRootKey: "server-root", SourcePath: "SCUM/Saved/SaveFiles/SCUM.db", WorkspaceKey: "databases/scum-database", RefreshPolicy: "on-demand-snapshot", MaxBytes: 1024 * 1024 * 1024, Platforms: []string{"windows"}}},
|
||||
},
|
||||
SCUMLiveData: domain.SCUMLiveDataManifest{SchemaVersion: "1", Probe: domain.SCUMSchemaProbeDeclaration{Capability: domain.JobCapabilityRemoteRunDBSQLiteProbe, TargetKey: "scum-database", Bounds: domain.DefaultSCUMSchemaProbeBounds()}, CapabilityGates: []domain.SCUMLiveDataCapabilityGateDeclaration{{Capability: domain.SCUMDataCapabilitySchemaProbe, Gate: domain.SCUMCapabilityGateDisabled, AdapterVersion: "scum-live-data-v0", EvidenceStatus: domain.SCUMCapabilityEvidenceMissing, SafeReason: "waiting for current service evidence"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create plugin: %v", err)
|
||||
}
|
||||
endpoint, err := core.CreateRunEndpoint(domain.RunEndpoint{ID: "run-local", DisplayName: "Local Run", Version: "0.1.0", Capabilities: []string{"process.start", domain.JobCapabilityRemoteRunDBSQLiteProbe}, Capacity: domain.RunCapacity{MaxJobs: 4}})
|
||||
endpoint, err := core.CreateRunEndpoint(domain.RunEndpoint{ID: "run-local", DisplayName: "Local Run", Version: "0.1.0", Capabilities: []string{"process.start", domain.JobCapabilityRemoteRunFilesRead, domain.JobCapabilityRemoteRunDBSQLiteProbe}, Capacity: domain.RunCapacity{MaxJobs: 4}})
|
||||
if err != nil {
|
||||
t.Fatalf("create run endpoint: %v", err)
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ type RunAutonomousLifecyclePlan struct {
|
||||
InstallPlans []RunAutonomousInstallPlan `json:"installPlans,omitempty"`
|
||||
LogSources []RunAutonomousLogSource `json:"logSources,omitempty"`
|
||||
DLLExtensions []RunAutonomousDLLExtension `json:"dllExtensions,omitempty"`
|
||||
DataTargets []RunAutonomousDataTarget `json:"dataTargets,omitempty"`
|
||||
RuntimeBindings map[string]string `json:"runtimeBindings,omitempty"`
|
||||
Deployment *RunAutonomousDeployment `json:"deployment,omitempty"`
|
||||
}
|
||||
@@ -208,6 +209,18 @@ type RunAutonomousDLLExtension struct {
|
||||
RCONPort int `json:"rconPort,omitempty"`
|
||||
}
|
||||
|
||||
type RunAutonomousDataTarget struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
TransportKey string `json:"transportKey"`
|
||||
SourceRootKey string `json:"sourceRootKey"`
|
||||
SourcePath string `json:"sourcePath"`
|
||||
WorkspaceKey string `json:"workspaceKey"`
|
||||
RefreshPolicy string `json:"refreshPolicy"`
|
||||
MaxBytes int64 `json:"maxBytes,omitempty"`
|
||||
Platforms []string `json:"platforms,omitempty"`
|
||||
}
|
||||
|
||||
type RunAutonomousDeployment struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
Mode ServerDeploymentMode `json:"mode"`
|
||||
@@ -497,6 +510,10 @@ func CopyRunAutonomousLifecyclePlanPtr(plan *RunAutonomousLifecyclePlan) *RunAut
|
||||
}
|
||||
copy.LogSources = append([]RunAutonomousLogSource(nil), plan.LogSources...)
|
||||
copy.DLLExtensions = append([]RunAutonomousDLLExtension(nil), plan.DLLExtensions...)
|
||||
copy.DataTargets = append([]RunAutonomousDataTarget(nil), plan.DataTargets...)
|
||||
for i := range copy.DataTargets {
|
||||
copy.DataTargets[i].Platforms = CopyStringSlice(plan.DataTargets[i].Platforms)
|
||||
}
|
||||
copy.RuntimeBindings = CopyStringMap(plan.RuntimeBindings)
|
||||
if plan.Deployment != nil {
|
||||
deployment := *plan.Deployment
|
||||
|
||||
@@ -479,6 +479,18 @@ type RuntimeTransportProfile struct {
|
||||
Capabilities []string
|
||||
}
|
||||
|
||||
type RuntimeDataTarget struct {
|
||||
Key string
|
||||
Kind string
|
||||
TransportKey string
|
||||
SourceRootKey string
|
||||
SourcePath string
|
||||
WorkspaceKey string
|
||||
RefreshPolicy string
|
||||
MaxBytes int64
|
||||
Platforms []string
|
||||
}
|
||||
|
||||
type RuntimeClientManagerProfile struct {
|
||||
Key string
|
||||
DisplayName string
|
||||
@@ -609,6 +621,7 @@ type GamePluginRuntimeProfiles struct {
|
||||
LogSources []RuntimeLogSource
|
||||
LogEvents []RuntimeLogEvent
|
||||
TransportProfiles []RuntimeTransportProfile
|
||||
DataTargets []RuntimeDataTarget
|
||||
ClientManagers []RuntimeClientManagerProfile
|
||||
DLLExtensions []RuntimeDLLExtensionProfile
|
||||
}
|
||||
@@ -1833,6 +1846,10 @@ func CopyGamePluginRuntimeProfiles(profiles GamePluginRuntimeProfiles) GamePlugi
|
||||
for i := range profiles.TransportProfiles {
|
||||
profiles.TransportProfiles[i].Capabilities = CopyStringSlice(profiles.TransportProfiles[i].Capabilities)
|
||||
}
|
||||
profiles.DataTargets = append([]RuntimeDataTarget(nil), profiles.DataTargets...)
|
||||
for i := range profiles.DataTargets {
|
||||
profiles.DataTargets[i].Platforms = CopyStringSlice(profiles.DataTargets[i].Platforms)
|
||||
}
|
||||
profiles.ClientManagers = append([]RuntimeClientManagerProfile(nil), profiles.ClientManagers...)
|
||||
for i := range profiles.ClientManagers {
|
||||
profiles.ClientManagers[i].SupportedTargets = append([]RuntimeTarget(nil), profiles.ClientManagers[i].SupportedTargets...)
|
||||
|
||||
@@ -127,6 +127,18 @@ type RuntimeTransportProfileBody struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
type RuntimeDataTargetBody struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
TransportKey string `json:"transportKey"`
|
||||
SourceRootKey string `json:"sourceRootKey"`
|
||||
SourcePath string `json:"sourcePath"`
|
||||
WorkspaceKey string `json:"workspaceKey"`
|
||||
RefreshPolicy string `json:"refreshPolicy"`
|
||||
MaxBytes int64 `json:"maxBytes,omitempty"`
|
||||
Platforms []string `json:"platforms,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeRepositoryBody struct {
|
||||
URL string `json:"url"`
|
||||
RevisionPolicy string `json:"revisionPolicy"`
|
||||
@@ -263,6 +275,7 @@ type GamePluginRuntimeProfilesBody struct {
|
||||
LogSources []RuntimeLogSourceBody `json:"logSources,omitempty"`
|
||||
LogEvents []RuntimeLogEventBody `json:"logEvents,omitempty"`
|
||||
TransportProfiles []RuntimeTransportProfileBody `json:"transportProfiles,omitempty"`
|
||||
DataTargets []RuntimeDataTargetBody `json:"dataTargets,omitempty"`
|
||||
ClientManagers []RuntimeClientManagerProfileBody `json:"clientManagers,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionProfileBody `json:"dllExtensions,omitempty"`
|
||||
}
|
||||
@@ -329,6 +342,9 @@ func (body GamePluginRuntimeProfilesBody) ToDomain() domain.GamePluginRuntimePro
|
||||
for _, item := range body.TransportProfiles {
|
||||
profiles.TransportProfiles = append(profiles.TransportProfiles, domain.RuntimeTransportProfile{Key: item.Key, Kind: item.Kind, TargetKey: item.TargetKey, Capabilities: domain.CopyStringSlice(item.Capabilities)})
|
||||
}
|
||||
for _, item := range body.DataTargets {
|
||||
profiles.DataTargets = append(profiles.DataTargets, domain.RuntimeDataTarget{Key: item.Key, Kind: item.Kind, TransportKey: item.TransportKey, SourceRootKey: item.SourceRootKey, SourcePath: item.SourcePath, WorkspaceKey: item.WorkspaceKey, RefreshPolicy: item.RefreshPolicy, MaxBytes: item.MaxBytes, Platforms: domain.CopyStringSlice(item.Platforms)})
|
||||
}
|
||||
for _, item := range body.ClientManagers {
|
||||
manager := domain.RuntimeClientManagerProfile{
|
||||
Key: item.Key, DisplayName: item.DisplayName, Version: item.Version,
|
||||
|
||||
@@ -268,6 +268,11 @@ func runAutonomousLifecyclePlan(distribution domain.RunDistribution, instance do
|
||||
plan.LogSources = append(plan.LogSources, autonomousLogSource(source))
|
||||
}
|
||||
}
|
||||
for _, target := range plugin.RuntimeProfiles.DataTargets {
|
||||
if runtimePlatformsContain(target.Platforms, distribution.TargetOS) {
|
||||
plan.DataTargets = append(plan.DataTargets, autonomousDataTarget(target))
|
||||
}
|
||||
}
|
||||
if hasProfile && len(profile.DLLExtensionRefs) > 0 {
|
||||
endpoint := domain.RunEndpoint{ID: distribution.RunEndpointID, Platform: distribution.TargetOS, Architecture: distribution.TargetArch}
|
||||
extensions, err := lifecycleDLLExtensionPlans(plugin.RuntimeProfiles, profile, endpoint)
|
||||
@@ -326,6 +331,10 @@ func autonomousDLLExtension(extension domain.RuntimeDLLExtensionPlan) domain.Run
|
||||
return domain.RunAutonomousDLLExtension{Key: extension.Key, Version: extension.Version, ReleaseURL: extension.ReleaseURL, Checksum: extension.Checksum, SizeBytes: extension.SizeBytes, TargetKey: extension.TargetKey, ModKey: extension.ModKey, DLLRef: extension.DLLRef, SCUMExecutableChecksum: extension.SCUMExecutableChecksum, UE4SSABI: extension.UE4SSABI, RCONPort: extension.RCONPort}
|
||||
}
|
||||
|
||||
func autonomousDataTarget(target domain.RuntimeDataTarget) domain.RunAutonomousDataTarget {
|
||||
return domain.RunAutonomousDataTarget{Key: target.Key, Kind: target.Kind, TransportKey: target.TransportKey, SourceRootKey: target.SourceRootKey, SourcePath: target.SourcePath, WorkspaceKey: target.WorkspaceKey, RefreshPolicy: target.RefreshPolicy, MaxBytes: target.MaxBytes, Platforms: domain.CopyStringSlice(target.Platforms)}
|
||||
}
|
||||
|
||||
func autonomousDeploymentFromDefinition(definition domain.ServerDeploymentDefinition, profileKey string, bindings map[string]string) *domain.RunAutonomousDeployment {
|
||||
if definition.Mode == "" {
|
||||
return nil
|
||||
|
||||
@@ -45,6 +45,13 @@ func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
|
||||
domain.RuntimeLogSource{Key: "console", Kind: "process.stdout", TargetKey: "server/process", StreamKey: "console", CursorKind: "sequence", RetentionDays: 14},
|
||||
domain.RuntimeLogSource{Key: "server-events", Kind: "file.tail", TargetKey: "logs/server", StreamKey: "scum.server", CursorKind: "fingerprint", RetentionDays: 90},
|
||||
)
|
||||
plugin.RuntimeProfiles.TransportProfiles = append(plugin.RuntimeProfiles.TransportProfiles,
|
||||
domain.RuntimeTransportProfile{Key: "server-files", Kind: "file", TargetKey: "server-root", Capabilities: []string{domain.JobCapabilityRemoteRunFilesRead}},
|
||||
domain.RuntimeTransportProfile{Key: "world-db", Kind: "sqlite", TargetKey: "world-db", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
|
||||
)
|
||||
plugin.RuntimeProfiles.DataTargets = append(plugin.RuntimeProfiles.DataTargets,
|
||||
domain.RuntimeDataTarget{Key: "world-db", Kind: "sqlite.snapshot", TransportKey: "world-db", SourceRootKey: "server-root", SourcePath: "world/current.db", WorkspaceKey: "databases/world-db", RefreshPolicy: "on-demand-snapshot", MaxBytes: 128 * 1024 * 1024, Platforms: []string{"linux"}},
|
||||
)
|
||||
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||
t.Fatalf("seed plugin lifecycle assets: %v", err)
|
||||
}
|
||||
@@ -90,14 +97,14 @@ func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
|
||||
if plan == nil || plan.SchemaVersion != "1" || plan.ServerInstanceID != instance.ID || plan.PluginID != plugin.ID || plan.ProfileKey != "local" || plan.Bootstrap == nil || plan.Bootstrap.Action != domain.ServerLifecycleActionStart || plan.Bootstrap.TargetKey != "actions/start.json" {
|
||||
t.Fatalf("platform builder received incomplete autonomous lifecycle plan: %+v", plan)
|
||||
}
|
||||
if len(plan.DependencyProbes) != 1 || plan.DependencyProbes[0].Key != "java-runtime" || len(plan.InstallPlans) != 1 || plan.InstallPlans[0].Key != "java-install" || len(plan.LogSources) != 3 || !hasAutonomousLogSource(plan.LogSources, "process.stdout", "console") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "latest-log") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "scum.server") || plan.RuntimeBindings["logs/latest"] != "runtime.logs.latest" {
|
||||
if len(plan.DependencyProbes) != 1 || plan.DependencyProbes[0].Key != "java-runtime" || len(plan.InstallPlans) != 1 || plan.InstallPlans[0].Key != "java-install" || len(plan.LogSources) != 3 || !hasAutonomousLogSource(plan.LogSources, "process.stdout", "console") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "latest-log") || !hasAutonomousLogSource(plan.LogSources, "file.tail", "scum.server") || len(plan.DataTargets) != 1 || plan.DataTargets[0].WorkspaceKey != "databases/world-db" || plan.DataTargets[0].SourcePath != "world/current.db" || plan.RuntimeBindings["logs/latest"] != "runtime.logs.latest" {
|
||||
t.Fatalf("autonomous lifecycle plan lost plugin runtime declarations: %+v", plan)
|
||||
}
|
||||
var seededPlan domain.RunAutonomousLifecyclePlan
|
||||
if err := json.Unmarshal([]byte(seedFiles[2].Content), &seededPlan); err != nil {
|
||||
t.Fatalf("unmarshal seeded autonomous lifecycle plan: %v", err)
|
||||
}
|
||||
if seededPlan.ServerInstanceID != plan.ServerInstanceID || seededPlan.Bootstrap == nil || seededPlan.Bootstrap.TargetKey != plan.Bootstrap.TargetKey {
|
||||
if seededPlan.ServerInstanceID != plan.ServerInstanceID || seededPlan.Bootstrap == nil || seededPlan.Bootstrap.TargetKey != plan.Bootstrap.TargetKey || len(seededPlan.DataTargets) != 1 || seededPlan.DataTargets[0].WorkspaceKey != "databases/world-db" {
|
||||
t.Fatalf("seeded lifecycle plan differs from build input: seed=%+v input=%+v", seededPlan, plan)
|
||||
}
|
||||
auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
|
||||
@@ -95,7 +95,11 @@ func TestSCUMSchemaProbeDispatchIsPlatformScheduledAndFenced(t *testing.T) {
|
||||
plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.remote.access")
|
||||
plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe)
|
||||
plugin.RemoteAccess = domain.GamePluginRemoteAccess{Methods: []string{"run"}, RunCapabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}, DatabaseEngines: []string{"sqlite"}}
|
||||
plugin.RuntimeProfiles.TransportProfiles = append(plugin.RuntimeProfiles.TransportProfiles, domain.RuntimeTransportProfile{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}})
|
||||
plugin.RuntimeProfiles.TransportProfiles = append(plugin.RuntimeProfiles.TransportProfiles,
|
||||
domain.RuntimeTransportProfile{Key: "server-files", Kind: "file", TargetKey: "server-root", Capabilities: []string{domain.JobCapabilityRemoteRunFilesRead}},
|
||||
domain.RuntimeTransportProfile{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
|
||||
)
|
||||
plugin.RuntimeProfiles.DataTargets = append(plugin.RuntimeProfiles.DataTargets, domain.RuntimeDataTarget{Key: "scum-database", Kind: "sqlite.snapshot", TransportKey: "scum-database", SourceRootKey: "server-root", SourcePath: "SCUM/Saved/SaveFiles/SCUM.db", WorkspaceKey: "databases/scum-database", RefreshPolicy: "on-demand-snapshot", MaxBytes: 1024 * 1024 * 1024, Platforms: []string{"windows"}})
|
||||
plugin.RuntimeProfiles.LifecycleProfiles[0].TransportKeys = append(plugin.RuntimeProfiles.LifecycleProfiles[0].TransportKeys, "scum-database")
|
||||
plugin.SCUMLiveData = domain.SCUMLiveDataManifest{SchemaVersion: "1", Probe: domain.SCUMSchemaProbeDeclaration{Capability: domain.JobCapabilityRemoteRunDBSQLiteProbe, TargetKey: "scum-database", Bounds: domain.DefaultSCUMSchemaProbeBounds()}, CapabilityGates: []domain.SCUMLiveDataCapabilityGateDeclaration{{Capability: domain.SCUMDataCapabilitySchemaProbe, Gate: domain.SCUMCapabilityGateDisabled, AdapterVersion: "scum-live-data-v0", EvidenceStatus: domain.SCUMCapabilityEvidenceMissing, SafeReason: "waiting for current service evidence"}}}
|
||||
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||
|
||||
@@ -28,6 +28,9 @@ func (svc *CoreService) RequestSCUMSchemaProbeForSession(sessionID, serverInstan
|
||||
if plugin.SCUMLiveData.SchemaVersion == "" || probe.Capability != domain.JobCapabilityRemoteRunDBSQLiteProbe || strings.TrimSpace(probe.TargetKey) == "" {
|
||||
return domain.SCUMSchemaProbeRequest{}, domain.RemoteAdapterResult{}, validationError("SCUM schema probe is not declared by the plugin")
|
||||
}
|
||||
if !scumSchemaProbeHasDataTarget(plugin.RuntimeProfiles, probe.TargetKey) {
|
||||
return domain.SCUMSchemaProbeRequest{}, domain.RemoteAdapterResult{}, validationError("SCUM schema probe target is not declared as a generated Run data target")
|
||||
}
|
||||
endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID)
|
||||
if err != nil {
|
||||
return domain.SCUMSchemaProbeRequest{}, domain.RemoteAdapterResult{}, err
|
||||
@@ -94,6 +97,16 @@ func scumSchemaProbeDatabaseIdentity(targetKey string) string {
|
||||
return strings.TrimPrefix(trimmed, "databases/")
|
||||
}
|
||||
|
||||
func scumSchemaProbeHasDataTarget(profiles domain.GamePluginRuntimeProfiles, targetKey string) bool {
|
||||
expectedWorkspaceKey := "databases/" + scumSchemaProbeDatabaseIdentity(targetKey)
|
||||
for _, target := range profiles.DataTargets {
|
||||
if target.Key == targetKey && target.Kind == "sqlite.snapshot" && target.WorkspaceKey == expectedWorkspaceKey && target.RefreshPolicy == "on-demand-snapshot" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func scumSchemaProbeAdapterVersion(manifest domain.SCUMLiveDataManifest) string {
|
||||
for _, gate := range manifest.CapabilityGates {
|
||||
if gate.Capability == domain.SCUMDataCapabilitySchemaProbe {
|
||||
|
||||
@@ -298,9 +298,13 @@ func TestValidateGamePluginManifestRegistrationRejectsUnsafeCapabilitiesAndPermi
|
||||
func TestValidateGamePluginManifestRegistrationValidatesSCUMLiveDataGate(t *testing.T) {
|
||||
registration := validGamePluginManifestRegistration()
|
||||
registration.Manifest.ID = "game.scum"
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe)
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe, domain.JobCapabilityRemoteRunFilesRead)
|
||||
registration.Manifest.RemoteAccess = domain.GamePluginRemoteAccess{Methods: []string{"run"}, RunCapabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}}
|
||||
registration.Manifest.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}}}
|
||||
registration.Manifest.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{
|
||||
{Key: "server-files", Kind: "file", TargetKey: "server-root", Capabilities: []string{domain.JobCapabilityRemoteRunFilesRead}},
|
||||
{Key: "scum-database", Kind: "sqlite", TargetKey: "scum-database", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteProbe}},
|
||||
}
|
||||
registration.Manifest.RuntimeProfiles.DataTargets = []domain.RuntimeDataTarget{{Key: "scum-database", Kind: "sqlite.snapshot", TransportKey: "scum-database", SourceRootKey: "server-root", SourcePath: "SCUM/Saved/SaveFiles/SCUM.db", WorkspaceKey: "databases/scum-database", RefreshPolicy: "on-demand-snapshot", MaxBytes: 1024 * 1024 * 1024, Platforms: []string{"windows"}}}
|
||||
registration.Manifest.SCUMLiveData = domain.SCUMLiveDataManifest{SchemaVersion: "1", Probe: domain.SCUMSchemaProbeDeclaration{Capability: domain.JobCapabilityRemoteRunDBSQLiteProbe, TargetKey: "scum-database", Bounds: domain.DefaultSCUMSchemaProbeBounds()}, CapabilityGates: []domain.SCUMLiveDataCapabilityGateDeclaration{{Capability: domain.SCUMDataCapabilityPlayerRead, Gate: domain.SCUMCapabilityGateDisabled, AdapterVersion: "scum-live-data-v0", EvidenceStatus: domain.SCUMCapabilityEvidenceMissing, SafeReason: "等待当前服务证据。"}}}
|
||||
|
||||
if err := ValidateGamePluginManifestRegistration(registration); err != nil {
|
||||
|
||||
@@ -34,6 +34,8 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
logSourceRetentions := map[string]int{}
|
||||
logEventKeys := map[string]struct{}{}
|
||||
logEventTypes := map[string]struct{}{}
|
||||
dataTargetKeys := map[string]struct{}{}
|
||||
transportTargetKeys := map[string]struct{}{}
|
||||
|
||||
for i, probe := range profiles.Discovery {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.discovery[%d]", i)
|
||||
@@ -301,6 +303,7 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
}
|
||||
if transport.TargetKey != "" {
|
||||
violations = append(violations, validateProfileKey(prefix+".targetKey", transport.TargetKey)...)
|
||||
transportTargetKeys[transport.TargetKey] = struct{}{}
|
||||
}
|
||||
if len(transport.Capabilities) == 0 {
|
||||
violations = append(violations, prefix+".capabilities must not be empty")
|
||||
@@ -312,6 +315,35 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
}
|
||||
violations = append(violations, duplicateViolations(prefix+".capabilities", transport.Capabilities)...)
|
||||
}
|
||||
for i, target := range profiles.DataTargets {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.dataTargets[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", target.Key)...)
|
||||
violations = append(violations, recordRuntimeProfileKey(dataTargetKeys, prefix+".key", target.Key)...)
|
||||
if target.Kind != "sqlite.snapshot" {
|
||||
violations = append(violations, prefix+".kind is invalid")
|
||||
}
|
||||
violations = append(violations, validateProfileKey(prefix+".transportKey", target.TransportKey)...)
|
||||
transport, transportExists := transportByKey(profiles.TransportProfiles, target.TransportKey)
|
||||
if !transportExists || transport.Kind != "sqlite" {
|
||||
violations = append(violations, prefix+".transportKey must reference a declared sqlite transport")
|
||||
}
|
||||
violations = append(violations, validateProfileKey(prefix+".sourceRootKey", target.SourceRootKey)...)
|
||||
if _, exists := transportTargetKeys[target.SourceRootKey]; !exists {
|
||||
violations = append(violations, prefix+".sourceRootKey must reference a declared runtime transport target")
|
||||
}
|
||||
violations = append(violations, validateSafeRelativeRuntimePath(prefix+".sourcePath", target.SourcePath)...)
|
||||
violations = append(violations, validateSafeRelativeRuntimePath(prefix+".workspaceKey", target.WorkspaceKey)...)
|
||||
if !strings.HasPrefix(target.WorkspaceKey, "databases/") {
|
||||
violations = append(violations, prefix+".workspaceKey must live under databases/")
|
||||
}
|
||||
if target.RefreshPolicy != "on-demand-snapshot" {
|
||||
violations = append(violations, prefix+".refreshPolicy is invalid")
|
||||
}
|
||||
if target.MaxBytes < 1 || target.MaxBytes > 1024*1024*1024 {
|
||||
violations = append(violations, prefix+".maxBytes is invalid")
|
||||
}
|
||||
violations = append(violations, validateRuntimePlatforms(prefix+".platforms", target.Platforms)...)
|
||||
}
|
||||
for i, manager := range profiles.ClientManagers {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.clientManagers[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", manager.Key)...)
|
||||
@@ -656,6 +688,15 @@ func recordRuntimeProfileKey(seen map[string]struct{}, field, key string) []stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func transportByKey(transports []domain.RuntimeTransportProfile, key string) (domain.RuntimeTransportProfile, bool) {
|
||||
for _, transport := range transports {
|
||||
if transport.Key == key {
|
||||
return transport, true
|
||||
}
|
||||
}
|
||||
return domain.RuntimeTransportProfile{}, false
|
||||
}
|
||||
|
||||
func validateRuntimeProfileCapabilityDeclarations(profiles domain.GamePluginRuntimeProfiles, declared []string) []string {
|
||||
declaredSet := map[string]struct{}{}
|
||||
for _, capability := range declared {
|
||||
|
||||
@@ -95,6 +95,8 @@ func validateSCUMLiveDataManifest(prefix string, value domain.SCUMLiveDataManife
|
||||
violations = append(violations, prefix+".probe requires declared remote.run.db.sqlite.probe capability")
|
||||
}
|
||||
probeTransportFound := false
|
||||
probeDataTargetFound := false
|
||||
expectedWorkspaceKey := "databases/" + strings.TrimPrefix(value.Probe.TargetKey, "databases/")
|
||||
for _, transport := range runtimeProfiles.TransportProfiles {
|
||||
if transport.Key != value.Probe.TargetKey && transport.TargetKey != value.Probe.TargetKey {
|
||||
continue
|
||||
@@ -107,6 +109,19 @@ func validateSCUMLiveDataManifest(prefix string, value domain.SCUMLiveDataManife
|
||||
if !probeTransportFound {
|
||||
violations = append(violations, prefix+".probe.targetKey must reference a declared transport")
|
||||
}
|
||||
for _, target := range runtimeProfiles.DataTargets {
|
||||
if target.Key != value.Probe.TargetKey {
|
||||
continue
|
||||
}
|
||||
probeDataTargetFound = true
|
||||
transport, ok := transportByKey(runtimeProfiles.TransportProfiles, target.TransportKey)
|
||||
if target.Kind != "sqlite.snapshot" || target.WorkspaceKey != expectedWorkspaceKey || !ok || transport.Kind != "sqlite" || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) {
|
||||
violations = append(violations, prefix+".probe.targetKey must reference a sqlite snapshot data target for the generated Run workspace")
|
||||
}
|
||||
}
|
||||
if !probeDataTargetFound {
|
||||
violations = append(violations, prefix+".probe.targetKey must reference a declared runtime data target")
|
||||
}
|
||||
violations = append(violations, validateSCUMSchemaProbeBounds(prefix+".probe.bounds", value.Probe.Bounds)...)
|
||||
seen := map[domain.SCUMDataCapability]struct{}{}
|
||||
for i, gate := range value.CapabilityGates {
|
||||
|
||||
Reference in New Issue
Block a user