Declare SCUM Run data targets
This commit is contained in:
@@ -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