Declare SCUM Run data targets

This commit is contained in:
npc0-hue
2026-08-12 17:44:53 +08:00
parent d854d6fda3
commit 2246859984
17 changed files with 235 additions and 10 deletions
+6 -2
View File
@@ -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 {
+41
View File
@@ -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 {
+15
View File
@@ -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 {