Revert SCUM real data management change

This commit is contained in:
npc0-hue
2026-08-13 15:33:34 +08:00
parent d831e4ade9
commit b07a792784
163 changed files with 5443 additions and 9174 deletions
-25
View File
@@ -51,31 +51,6 @@ func ValidateRunJobResult(result domain.RunJobResult) error {
if result.ExecutionResult.Checksum != "" && !validSHA256Checksum(result.ExecutionResult.Checksum) {
violations = append(violations, "executionResult.checksum must be sha256:<hex>")
}
if result.ExecutionResult.SQLiteSchemaProbe != nil {
if err := ValidateSCUMSchemaProbeResult(*result.ExecutionResult.SQLiteSchemaProbe); err != nil {
violations = append(violations, "executionResult.sqliteSchemaProbe: "+err.Error())
}
}
if result.ExecutionResult.SQLiteTemplate != nil {
if err := ValidateSCUMSQLiteTemplateResult(*result.ExecutionResult.SQLiteTemplate); err != nil {
violations = append(violations, "executionResult.sqliteTemplate: "+err.Error())
}
}
if result.ExecutionResult.RCONTemplate != nil {
if err := ValidateSCUMTypedRCONTemplateResult(*result.ExecutionResult.RCONTemplate); err != nil {
violations = append(violations, "executionResult.rconTemplate: "+err.Error())
}
}
if result.ExecutionResult.GuardedMutation != nil {
if err := ValidateSCUMGuardedMutationResult(*result.ExecutionResult.GuardedMutation); err != nil {
violations = append(violations, "executionResult.guardedMutation: "+err.Error())
}
}
if result.ExecutionResult.ParsedLogBatch != nil {
if err := ValidateSCUMParsedLogBatchResult(*result.ExecutionResult.ParsedLogBatch); err != nil {
violations = append(violations, "executionResult.parsedLogBatch: "+err.Error())
}
}
return finish(violations)
}
+1 -61
View File
@@ -157,7 +157,6 @@ func ValidateGamePlugin(plugin domain.GamePlugin) error {
violations = append(violations, validateRuntimeProfileCapabilityDeclarations(plugin.RuntimeProfiles, plugin.RequiredRunCapabilities)...)
violations = append(violations, validateRuntimeLogEventPermissionDeclarations("runtimeProfiles.logEvents", plugin.RuntimeProfiles, plugin.DeclaredPermissions)...)
violations = append(violations, validateGameClientBridgeManifest("gameClientBridge", plugin.GameClientBridge, plugin.DeclaredPermissions, plugin.Pages, plugin.RuntimeProfiles)...)
violations = append(violations, validateSCUMLiveDataManifest("scumLiveData", plugin.SCUMLiveData, plugin.RequiredRunCapabilities, plugin.RemoteAccess, plugin.RuntimeProfiles)...)
violations = append(violations, validateMapTrajectoryDeclaration("mapTrajectories", plugin.MapTrajectories)...)
violations = append(violations, validatePluginCreateFields("createFields", plugin.CreateFields)...)
violations = append(violations, validatePluginAssetFiles("lifecycleAssets", plugin.LifecycleAssets)...)
@@ -230,7 +229,6 @@ func ValidateGamePluginManifestRegistration(registration domain.GamePluginManife
violations = append(violations, validateRuntimeProfileCapabilityDeclarations(manifest.RuntimeProfiles, manifest.Capabilities)...)
violations = append(violations, validateRuntimeLogEventPermissionDeclarations("manifest.runtimeProfiles.logEvents", manifest.RuntimeProfiles, manifest.Permissions)...)
violations = append(violations, validateGameClientBridgeManifest("manifest.gameClientBridge", manifest.GameClientBridge, manifest.Permissions, manifest.Pages, manifest.RuntimeProfiles)...)
violations = append(violations, validateSCUMLiveDataManifest("manifest.scumLiveData", manifest.SCUMLiveData, manifest.Capabilities, manifest.RemoteAccess, manifest.RuntimeProfiles)...)
violations = append(violations, validateMapTrajectoryDeclaration("manifest.mapTrajectories", manifest.MapTrajectories)...)
violations = append(violations, validatePluginAssetFileDeclarations("manifest.assetFiles", manifest.AssetFiles)...)
violations = append(violations, validatePluginAssetFiles("assetFiles", registration.AssetFiles)...)
@@ -1533,39 +1531,6 @@ func ValidateJob(job domain.Job) error {
violations = append(violations, "executionInput.sourceRcon must not persist adapter inputs")
}
}
if job.ExecutionInput.SQLiteTemplate != nil {
if err := ValidateSCUMSQLiteTemplateRequest(*job.ExecutionInput.SQLiteTemplate); err != nil {
violations = append(violations, "executionInput.sqliteTemplate: "+err.Error())
}
if job.Capability != domain.JobCapabilityRemoteRunDBSQLiteQuery {
violations = append(violations, "executionInput.sqliteTemplate is allowed only for sqlite query jobs")
}
}
if job.ExecutionInput.RCONTemplate != nil {
if err := ValidateSCUMTypedRCONTemplateRequest(*job.ExecutionInput.RCONTemplate); err != nil {
violations = append(violations, "executionInput.rconTemplate: "+err.Error())
}
if job.Capability != domain.JobCapabilityRemoteRunProtectedRCON {
violations = append(violations, "executionInput.rconTemplate is allowed only for protected rcon jobs")
}
if job.ExecutionInput.SourceRCON == nil || job.ExecutionInput.RemoteAdapterKind != "protected-rcon" {
violations = append(violations, "executionInput.rconTemplate requires a protected rcon transport plan")
}
}
if job.ExecutionInput.GuardedMutation != nil {
if err := ValidateSCUMGuardedMutationRequest(*job.ExecutionInput.GuardedMutation); err != nil {
violations = append(violations, "executionInput.guardedMutation: "+err.Error())
}
if job.Capability != domain.JobCapabilityRemoteRunProtectedSQL {
violations = append(violations, "executionInput.guardedMutation is allowed only for protected sql jobs")
}
if job.ExecutionInput.RemoteAdapterKind != "protected-sql" {
violations = append(violations, "executionInput.guardedMutation requires a protected sql transport plan")
}
if len(job.ExecutionInput.Inputs) != 0 || job.ExecutionInput.Content != "" {
violations = append(violations, "executionInput.guardedMutation must not persist raw adapter inputs")
}
}
violations = append(violations, validateRemoteAdapterInputs("executionInput.inputs", job.ExecutionInput.Inputs)...)
if job.ExecutionResult.Checksum != "" && !validSHA256Checksum(job.ExecutionResult.Checksum) {
violations = append(violations, "executionResult.checksum must be sha256:<hex>")
@@ -1576,31 +1541,6 @@ func ValidateJob(job domain.Job) error {
if len(job.ExecutionResult.AuditSummary) > maxAuditSummaryLength {
violations = append(violations, "executionResult.auditSummary is too long")
}
if job.ExecutionResult.SQLiteSchemaProbe != nil {
if err := ValidateSCUMSchemaProbeResult(*job.ExecutionResult.SQLiteSchemaProbe); err != nil {
violations = append(violations, "executionResult.sqliteSchemaProbe: "+err.Error())
}
}
if job.ExecutionResult.SQLiteTemplate != nil {
if err := ValidateSCUMSQLiteTemplateResult(*job.ExecutionResult.SQLiteTemplate); err != nil {
violations = append(violations, "executionResult.sqliteTemplate: "+err.Error())
}
}
if job.ExecutionResult.RCONTemplate != nil {
if err := ValidateSCUMTypedRCONTemplateResult(*job.ExecutionResult.RCONTemplate); err != nil {
violations = append(violations, "executionResult.rconTemplate: "+err.Error())
}
}
if job.ExecutionResult.GuardedMutation != nil {
if err := ValidateSCUMGuardedMutationResult(*job.ExecutionResult.GuardedMutation); err != nil {
violations = append(violations, "executionResult.guardedMutation: "+err.Error())
}
}
if job.ExecutionResult.ParsedLogBatch != nil {
if err := ValidateSCUMParsedLogBatchResult(*job.ExecutionResult.ParsedLogBatch); err != nil {
violations = append(violations, "executionResult.parsedLogBatch: "+err.Error())
}
}
if job.Capability == domain.JobCapabilityConfigWrite || job.Capability == domain.JobCapabilityFilesRead || job.Capability == domain.JobCapabilityFilesWrite {
if job.ServerInstanceID == "" {
violations = append(violations, "serverInstanceId is required for scoped file jobs")
@@ -2259,7 +2199,7 @@ func validPluginRunCapability(capability string) bool {
domain.JobCapabilityRemoteRsyncRead, domain.JobCapabilityRemoteRsyncWrite,
domain.JobCapabilityRemoteRunFilesRead, domain.JobCapabilityRemoteRunFilesWrite,
domain.JobCapabilityRemoteRunProcessStart, domain.JobCapabilityRemoteRunProcessStop,
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteProbe, domain.JobCapabilityRemoteRunDBSQLiteQuery,
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteQuery,
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
domain.JobCapabilityRemoteRunProtectedSQL, domain.JobCapabilityRemoteRunProtectedRCON, domain.JobCapabilityRemoteRunProgram,
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
-23
View File
@@ -295,29 +295,6 @@ func TestValidateGamePluginManifestRegistrationRejectsUnsafeCapabilitiesAndPermi
}
}
func TestValidateGamePluginManifestRegistrationValidatesSCUMLiveDataGate(t *testing.T) {
registration := validGamePluginManifestRegistration()
registration.Manifest.ID = "game.scum"
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: "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 {
t.Fatalf("expected disabled live-data gate to validate, got %v", err)
}
registration.Manifest.SCUMLiveData.CapabilityGates[0].Gate = domain.SCUMCapabilityGateEnabled
err := ValidateGamePluginManifestRegistration(registration)
if err == nil || !strings.Contains(err.Error(), "evidenceStatus must be compatible") || !strings.Contains(err.Error(), "requiredSchemaFingerprint") {
t.Fatalf("expected enabled gate evidence violations, got %v", err)
}
}
func TestValidateServerInstanceDependencies(t *testing.T) {
instance := domain.ServerInstance{
ID: "server-1",
-41
View File
@@ -34,8 +34,6 @@ 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)
@@ -303,7 +301,6 @@ 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")
@@ -315,35 +312,6 @@ 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)...)
@@ -688,15 +656,6 @@ 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 {
-981
View File
@@ -1,981 +0,0 @@
package validator
import (
"encoding/json"
"fmt"
"net"
"regexp"
"strings"
"browser.local/platform/domain"
)
const (
maxSCUMProbeObjects = 512
maxSCUMProbeColumnsPerObject = 256
maxSCUMProbeIndexesPerObject = 128
maxSCUMProbeForeignKeys = 128
maxSCUMProbeSamples = 3
maxSCUMProbeTimeoutMS = 10000
maxSCUMProbeResultBytes = 1024 * 1024
maxSCUMTemplateParameters = 64
maxSCUMTemplateRows = 1000
maxSCUMTemplateBusyTimeoutMS = 1000
maxSCUMTemplateValueBytes = 4096
maxSCUMRCONPayloadBytes = 4096
maxSCUMRCONResponseBytes = 64 * 1024
maxSCUMRCONConfirmRecords = 128
maxSCUMMutationPayloadBytes = 4096
maxSCUMMutationReadbackBytes = 64 * 1024
maxSCUMParsedLogEvents = 1024
maxSCUMParsedLogPayloadBytes = 64 * 1024
maxSCUMParsedLogLineBytes = 64 * 1024
maxSCUMParsedLogResultBytes = 1024 * 1024
)
var scumHashPattern = regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$|^[a-fA-F0-9]{16,128}$`)
var scumDataTargetSafeErrorPattern = regexp.MustCompile(`^data_target_[a-z0-9_]{1,80}$`)
var scumTemplateKeyPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_.-]{0,127}$`)
func ValidateSCUMSchemaProbeRequest(request domain.SCUMSchemaProbeRequest) error {
var violations []string
violations = appendRequired(violations, "requestId", request.RequestID)
violations = appendRequired(violations, "jobId", request.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...)
violations = append(violations, validateSCUMSchemaProbeBounds("bounds", request.Bounds)...)
return finish(violations)
}
func ValidateSCUMSQLiteTemplateRequest(request domain.SCUMSQLiteTemplateRequest) error {
var violations []string
violations = appendRequired(violations, "requestId", request.RequestID)
violations = appendRequired(violations, "jobId", request.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...)
if !validSCUMReadCapability(request.Capability) {
violations = append(violations, "capability must be a read capability")
}
violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", request.AdapterVersion)
if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if !validSCUMFingerprint(request.RequiredSchemaFingerprint) {
violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint")
}
if !validSCUMDigest(request.AssetDigest) {
violations = append(violations, "assetDigest must be sha256 digest")
}
if !validSCUMDigest(request.ParameterDigest) {
violations = append(violations, "parameterDigest must be sha256 digest")
}
violations = append(violations, validateSCUMSQLiteTemplateBounds("bounds", request.Bounds)...)
violations = append(violations, validateSCUMValueMap("parameters", request.Parameters, request.Bounds.MaxParameters)...)
return finish(violations)
}
func ValidateSCUMTypedRCONTemplateRequest(request domain.SCUMTypedRCONTemplateRequest) error {
var violations []string
violations = appendRequired(violations, "requestId", request.RequestID)
violations = appendRequired(violations, "jobId", request.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...)
if !validSCUMRCONWriteCapability(request.Capability) {
violations = append(violations, "capability must be a typed RCON write capability")
}
violations = append(violations, validateSCUMTemplateKey("transportKey", request.TransportKey)...)
violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", request.AdapterVersion)
if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if request.RequiredSchemaFingerprint != "" && !validSCUMFingerprint(request.RequiredSchemaFingerprint) {
violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint")
}
for _, item := range []struct{ name, value string }{{"assetDigest", request.AssetDigest}, {"payloadDigest", request.PayloadDigest}, {"confirmationDigest", request.ConfirmationDigest}, {"targetIdentityDigest", request.TargetIdentityDigest}} {
if !validSCUMDigest(item.value) {
violations = append(violations, item.name+" must be sha256 digest")
}
}
violations = append(violations, validateSCUMTemplateKey("idempotencyKey", request.IdempotencyKey)...)
if strings.TrimSpace(request.ReviewReason) == "" || len(request.ReviewReason) > 320 || containsSCUMProtectedMaterial(request.ReviewReason) {
violations = append(violations, "reviewReason is unsafe")
}
violations = append(violations, validateSCUMTypedRCONTemplateBounds("bounds", request.Bounds)...)
violations = append(violations, validateSCUMValueMap("payload", request.Payload, 64)...)
violations = append(violations, validateSCUMJSONSize("payload", request.Payload, request.Bounds.MaxPayloadBytes)...)
return finish(violations)
}
func ValidateSCUMGuardedMutationRequest(request domain.SCUMGuardedMutationRequest) error {
var violations []string
violations = appendRequired(violations, "requestId", request.RequestID)
violations = appendRequired(violations, "jobId", request.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...)
if !validSCUMGuardedMutationCapability(request.Capability) {
violations = append(violations, "capability must be a guarded database/XML write capability")
}
violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", request.AdapterVersion)
if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if !validSCUMFingerprint(request.RequiredSchemaFingerprint) {
violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint")
}
for _, item := range scumGuardedMutationRequestDigests(request) {
if !validSCUMDigest(item.value) {
violations = append(violations, item.name+" must be sha256 digest")
}
}
violations = append(violations, validateSCUMTemplateKey("idempotencyKey", request.IdempotencyKey)...)
if strings.TrimSpace(request.ReviewReason) == "" || len(request.ReviewReason) > 320 || containsSCUMProtectedMaterial(request.ReviewReason) || containsSCUMRawXML(request.ReviewReason) {
violations = append(violations, "reviewReason is unsafe")
}
violations = append(violations, validateSCUMGuardedMutationBounds("bounds", request.Bounds)...)
violations = append(violations, validateSCUMGuardedMutationPayload("payload", request.Payload)...)
violations = append(violations, validateSCUMJSONSize("payload", request.Payload, request.Bounds.MaxPayloadBytes)...)
return finish(violations)
}
func ValidateSCUMSchemaProbeResult(result domain.SCUMSchemaProbeResult) error {
var violations []string
violations = appendRequired(violations, "requestId", result.RequestID)
violations = appendRequired(violations, "jobId", result.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...)
if !validSCUMSchemaProbeResultStatus(result.Status) {
violations = append(violations, "status is invalid")
}
if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) {
violations = append(violations, "sourceFingerprint must be a digest/fingerprint")
}
if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) {
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
}
if result.ResultDigest != "" && !validSCUMFingerprint(result.ResultDigest) {
violations = append(violations, "resultDigest must be a digest/fingerprint")
}
violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...)
violations = append(violations, validateSCUMSchemaProbeBounds("limits", result.Limits)...)
if len(result.Objects) > result.Limits.MaxObjects && result.Limits.MaxObjects > 0 {
violations = append(violations, "objects exceeds declared limit")
}
for i, object := range result.Objects {
field := fmt.Sprintf("objects[%d]", i)
violations = append(violations, validateSCUMSchemaObjectEvidence(field, object)...)
}
return finish(violations)
}
func ValidateSCUMSQLiteTemplateResult(result domain.SCUMSQLiteTemplateResult) error {
var violations []string
violations = appendRequired(violations, "requestId", result.RequestID)
violations = appendRequired(violations, "jobId", result.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...)
if !validSCUMTerminalResultStatus(result.Status) {
violations = append(violations, "status is invalid")
}
if !validSCUMReadCapability(result.Capability) {
violations = append(violations, "capability must be a read capability")
}
violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", result.AdapterVersion)
if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) {
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
}
if !validSCUMDigest(result.AssetDigest) {
violations = append(violations, "assetDigest must be sha256 digest")
}
if !validSCUMDigest(result.ParameterDigest) {
violations = append(violations, "parameterDigest must be sha256 digest")
}
if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) {
violations = append(violations, "sourceFingerprint must be a digest/fingerprint")
}
if !validSCUMFingerprint(result.ResultDigest) {
violations = append(violations, "resultDigest must be a digest/fingerprint")
}
violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...)
violations = append(violations, validateSCUMSQLiteTemplateBounds("limits", result.Limits)...)
if result.RowCount != len(result.Rows) {
violations = append(violations, "rowCount must match returned rows")
}
if len(result.Rows) > result.Limits.MaxRows && result.Limits.MaxRows > 0 {
violations = append(violations, "rows exceeds declared limit")
}
for i, row := range result.Rows {
violations = append(violations, validateSCUMValueMap(fmt.Sprintf("rows[%d]", i), row, 256)...)
}
if result.Status == domain.SCUMTerminalResultSucceeded {
if result.SchemaFingerprint == "" || result.SourceFingerprint == "" {
violations = append(violations, "succeeded result requires schema/source fingerprints")
}
if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone {
violations = append(violations, "succeeded result must not carry an error code")
}
} else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone {
violations = append(violations, "non-succeeded result requires a safe error code")
}
return finish(violations)
}
func ValidateSCUMTypedRCONTemplateResult(result domain.SCUMTypedRCONTemplateResult) error {
var violations []string
violations = appendRequired(violations, "requestId", result.RequestID)
violations = appendRequired(violations, "jobId", result.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...)
if !validSCUMTerminalResultStatus(result.Status) {
violations = append(violations, "status is invalid")
}
if !validSCUMRCONWriteCapability(result.Capability) {
violations = append(violations, "capability must be a typed RCON write capability")
}
violations = append(violations, validateSCUMTemplateKey("transportKey", result.TransportKey)...)
violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", result.AdapterVersion)
if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) {
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
}
for _, item := range []struct{ name, value string }{{"assetDigest", result.AssetDigest}, {"payloadDigest", result.PayloadDigest}, {"confirmationDigest", result.ConfirmationDigest}, {"targetIdentityDigest", result.TargetIdentityDigest}, {"resultDigest", result.ResultDigest}} {
if !validSCUMDigest(item.value) {
violations = append(violations, item.name+" must be sha256 digest")
}
}
if result.ResponseDigest != "" && !validSCUMDigest(result.ResponseDigest) {
violations = append(violations, "responseDigest must be sha256 digest")
}
if result.ConfirmationDigestID != "" && !validSCUMDigest(result.ConfirmationDigestID) {
violations = append(violations, "confirmationDigestId must be sha256 digest")
}
if !validSCUMRCONConfirmationStatus(result.ConfirmationStatus) {
violations = append(violations, "confirmationStatus is invalid")
}
if len(result.SafeSummary) > 320 || containsSCUMProtectedMaterial(result.SafeSummary) {
violations = append(violations, "safeSummary is unsafe")
}
violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...)
violations = append(violations, validateSCUMTypedRCONTemplateBounds("limits", result.Limits)...)
if result.Status == domain.SCUMTerminalResultSucceeded {
if result.ConfirmationStatus != domain.SCUMRCONConfirmationConfirmed || result.ResponseDigest == "" || result.ConfirmationDigestID == "" {
violations = append(violations, "succeeded result requires confirmed response and confirmation digests")
}
if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone {
violations = append(violations, "succeeded result must not carry an error code")
}
} else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone {
violations = append(violations, "non-succeeded result requires a safe error code")
}
return finish(violations)
}
func ValidateSCUMGuardedMutationResult(result domain.SCUMGuardedMutationResult) error {
var violations []string
violations = appendRequired(violations, "requestId", result.RequestID)
violations = appendRequired(violations, "jobId", result.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...)
if !validSCUMTerminalResultStatus(result.Status) {
violations = append(violations, "status is invalid")
}
if !validSCUMGuardedMutationCapability(result.Capability) {
violations = append(violations, "capability must be a guarded database/XML write capability")
}
violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...)
violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...)
violations = appendRequired(violations, "adapterVersion", result.AdapterVersion)
if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) {
violations = append(violations, "adapterVersion contains protected material")
}
if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion {
violations = append(violations, "adapterVersion must match binding.adapterVersion")
}
if !validSCUMFingerprint(result.SchemaFingerprint) {
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
}
if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) {
violations = append(violations, "sourceFingerprint must be a digest/fingerprint")
}
for _, item := range scumGuardedMutationResultDigests(result) {
if !validSCUMDigest(item.value) {
violations = append(violations, item.name+" must be sha256 digest")
}
}
if !validSCUMMutationReadbackStatus(result.ReadbackStatus) {
violations = append(violations, "readbackStatus is invalid")
}
if len(result.SafeSummary) > 320 || containsSCUMProtectedMaterial(result.SafeSummary) || containsSCUMRawXML(result.SafeSummary) {
violations = append(violations, "safeSummary is unsafe")
}
violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...)
violations = append(violations, validateSCUMGuardedMutationBounds("limits", result.Limits)...)
if result.Status == domain.SCUMTerminalResultSucceeded {
if result.AffectedRows != 1 || result.Limits.MaxAffectedRows != 1 {
violations = append(violations, "succeeded result requires exactly one affected row")
}
if result.ReadbackStatus != domain.SCUMMutationReadbackConfirmed || result.SourceFingerprint == "" || result.BeforeDigest == "" || result.AfterDigest == "" || result.ReadbackDigest == "" {
violations = append(violations, "succeeded result requires conclusive before/after/readback digests")
}
if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone {
violations = append(violations, "succeeded result must not carry an error code")
}
} else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone {
violations = append(violations, "non-succeeded result requires a safe error code")
}
return finish(violations)
}
func ValidateSCUMParsedLogBatchResult(result domain.SCUMParsedLogBatchResult) error {
var violations []string
violations = appendRequired(violations, "requestId", result.RequestID)
violations = appendRequired(violations, "jobId", result.JobID)
violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...)
if !validSCUMTerminalResultStatus(result.Status) {
violations = append(violations, "status is invalid")
}
for _, item := range []struct{ name, value string }{{"sourceKey", result.SourceKey}, {"streamKey", result.StreamKey}, {"parserKey", result.ParserKey}, {"parserVersion", result.ParserVersion}, {"adapterVersion", result.AdapterVersion}} {
violations = append(violations, validateSCUMTemplateKey(item.name, item.value)...)
if containsSCUMProtectedMaterial(item.value) || containsSCUMNetworkMaterial(item.value) {
violations = append(violations, item.name+" contains protected material")
}
}
for _, item := range []struct{ name, value string }{{"assetDigest", result.AssetDigest}, {"parserDigest", result.ParserDigest}, {"resultDigest", result.ResultDigest}} {
if !validSCUMDigest(item.value) {
violations = append(violations, item.name+" must be sha256 digest")
}
}
violations = append(violations, validateSCUMParsedLogCursor("firstCursor", result.FirstCursor)...)
violations = append(violations, validateSCUMParsedLogCursor("lastCursor", result.LastCursor)...)
if !validSCUMLogTailState(result.TailState) {
violations = append(violations, "tailState is invalid")
}
if result.EventCount != len(result.Events) {
violations = append(violations, "eventCount must match returned events")
}
violations = append(violations, validateSCUMParsedLogBatchBounds("limits", result.Limits)...)
if len(result.Events) > result.Limits.MaxEvents && result.Limits.MaxEvents > 0 {
violations = append(violations, "events exceeds declared limit")
}
if len(result.SafeSummary) > 320 || containsSCUMProtectedMaterial(result.SafeSummary) || containsSCUMNetworkMaterial(result.SafeSummary) || containsSCUMRawXML(result.SafeSummary) {
violations = append(violations, "safeSummary is unsafe")
}
violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...)
if containsSCUMNetworkMaterial(result.SafeError.Message) || containsSCUMRawXML(result.SafeError.Message) {
violations = append(violations, "safeError.message is unsafe")
}
seenLogical := map[string]struct{}{}
seenTransport := map[string]struct{}{}
for i, event := range result.Events {
field := fmt.Sprintf("events[%d]", i)
violations = append(violations, validateSCUMParsedLogEvent(field, event, result)...)
if event.LogicalEventDigest != "" {
if _, exists := seenLogical[event.LogicalEventDigest]; exists {
violations = append(violations, field+".logicalEventDigest is duplicated")
}
seenLogical[event.LogicalEventDigest] = struct{}{}
}
transport := fmt.Sprintf("%s|%s|%d", event.Cursor.SourceIdentityDigest, event.Cursor.StreamGeneration, event.Cursor.Sequence)
if _, exists := seenTransport[transport]; exists {
violations = append(violations, field+".cursor is duplicated")
}
seenTransport[transport] = struct{}{}
}
if result.EventCount > 0 && result.LastCursor.Sequence < result.FirstCursor.Sequence {
violations = append(violations, "lastCursor.sequence must not be before firstCursor.sequence")
}
if result.Status == domain.SCUMTerminalResultSucceeded {
if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone {
violations = append(violations, "succeeded result must not carry an error code")
}
} else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone {
violations = append(violations, "non-succeeded result requires a safe error code")
}
return finish(violations)
}
func scumGuardedMutationRequestDigests(request domain.SCUMGuardedMutationRequest) []struct{ name, value string } {
return []struct{ name, value string }{{"assetDigest", request.AssetDigest}, {"targetIdentityDigest", request.TargetIdentityDigest}, {"expectedRowDigest", request.ExpectedRowDigest}, {"expectedValueDigest", request.ExpectedValueDigest}, {"expectedXmlDigest", request.ExpectedXMLDigest}, {"patchDigest", request.PatchDigest}, {"backupEvidenceDigest", request.BackupEvidenceDigest}, {"offlineEvidenceDigest", request.OfflineEvidenceDigest}, {"dangerConfirmationDigest", request.DangerConfirmationDigest}, {"readbackExpectationDigest", request.ReadbackExpectationDigest}}
}
func scumGuardedMutationResultDigests(result domain.SCUMGuardedMutationResult) []struct{ name, value string } {
items := []struct{ name, value string }{{"assetDigest", result.AssetDigest}, {"targetIdentityDigest", result.TargetIdentityDigest}, {"expectedRowDigest", result.ExpectedRowDigest}, {"expectedValueDigest", result.ExpectedValueDigest}, {"expectedXmlDigest", result.ExpectedXMLDigest}, {"patchDigest", result.PatchDigest}, {"backupEvidenceDigest", result.BackupEvidenceDigest}, {"offlineEvidenceDigest", result.OfflineEvidenceDigest}, {"dangerConfirmationDigest", result.DangerConfirmationDigest}, {"readbackExpectationDigest", result.ReadbackExpectationDigest}, {"resultDigest", result.ResultDigest}}
for _, item := range []struct{ name, value string }{{"beforeDigest", result.BeforeDigest}, {"afterDigest", result.AfterDigest}, {"readbackDigest", result.ReadbackDigest}} {
if item.value != "" {
items = append(items, item)
}
}
return items
}
func ValidateSCUMCapabilityEvidence(evidence domain.SCUMCapabilityEvidence) error {
var violations []string
if !validSCUMDataCapability(evidence.Capability) {
violations = append(violations, "capability is invalid")
}
if !validSCUMCapabilityEvidenceStatus(evidence.Status) {
violations = append(violations, "status is invalid")
}
violations = append(violations, validateSCUMBindingIdentity("binding", evidence.Binding)...)
violations = appendRequired(violations, "adapterVersion", evidence.AdapterVersion)
if evidence.SchemaFingerprint != "" && !validSCUMFingerprint(evidence.SchemaFingerprint) {
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
}
if evidence.ProbeResultDigest != "" && !validSCUMFingerprint(evidence.ProbeResultDigest) {
violations = append(violations, "probeResultDigest must be a digest/fingerprint")
}
for i, digest := range evidence.AssetDigests {
if !validSCUMDigest(digest) {
violations = append(violations, fmt.Sprintf("assetDigests[%d] must be sha256 digest", i))
}
}
violations = append(violations, validateSCUMSafeError("safeError", evidence.SafeError)...)
return finish(violations)
}
func validateSCUMLiveDataManifest(prefix string, value domain.SCUMLiveDataManifest, capabilities []string, remoteAccess domain.GamePluginRemoteAccess, runtimeProfiles domain.GamePluginRuntimeProfiles) []string {
if value.SchemaVersion == "" && value.Probe.Capability == "" && len(value.CapabilityGates) == 0 {
return nil
}
var violations []string
if value.SchemaVersion != "1" {
violations = append(violations, prefix+".schemaVersion must be 1")
}
if value.Probe.Capability != domain.JobCapabilityRemoteRunDBSQLiteProbe {
violations = append(violations, prefix+".probe.capability must be "+domain.JobCapabilityRemoteRunDBSQLiteProbe)
}
if !containsString(capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) || !containsString(remoteAccess.RunCapabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) {
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
}
probeTransportFound = true
if transport.Kind != "sqlite" || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) {
violations = append(violations, prefix+".probe.targetKey must reference sqlite transport with remote.run.db.sqlite.probe")
}
}
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 {
field := fmt.Sprintf("%s.capabilityGates[%d]", prefix, i)
if !validSCUMDataCapability(gate.Capability) {
violations = append(violations, field+".capability is invalid")
}
if _, exists := seen[gate.Capability]; exists {
violations = append(violations, field+".capability is duplicated")
}
seen[gate.Capability] = struct{}{}
if gate.Gate != domain.SCUMCapabilityGateDisabled && gate.Gate != domain.SCUMCapabilityGateEnabled {
violations = append(violations, field+".gate is invalid")
}
violations = appendRequired(violations, field+".adapterVersion", gate.AdapterVersion)
if !validSCUMCapabilityEvidenceStatus(gate.EvidenceStatus) {
violations = append(violations, field+".evidenceStatus is invalid")
}
if containsSCUMProtectedMaterial(gate.SafeReason) || len(gate.SafeReason) > 240 || strings.TrimSpace(gate.SafeReason) == "" {
violations = append(violations, field+".safeReason is unsafe")
}
if gate.Gate == domain.SCUMCapabilityGateEnabled {
if gate.EvidenceStatus != domain.SCUMCapabilityEvidenceCompatible {
violations = append(violations, field+".evidenceStatus must be compatible when enabled")
}
if !validSCUMFingerprint(gate.RequiredSchemaFingerprint) {
violations = append(violations, field+".requiredSchemaFingerprint is required when enabled")
}
if gate.Capability != domain.SCUMDataCapabilitySchemaProbe && len(gate.RequiredAssetDigests) == 0 {
violations = append(violations, field+".requiredAssetDigests is required when enabled")
}
}
if gate.Gate == domain.SCUMCapabilityGateDisabled && gate.EvidenceStatus == domain.SCUMCapabilityEvidenceCompatible {
violations = append(violations, field+".evidenceStatus must not claim compatibility while disabled")
}
for digestIndex, digest := range gate.RequiredAssetDigests {
if !validSCUMDigest(digest) {
violations = append(violations, fmt.Sprintf("%s.requiredAssetDigests[%d] must be sha256 digest", field, digestIndex))
}
}
}
return violations
}
func validateSCUMBindingIdentity(prefix string, value domain.SCUMBindingIdentity) []string {
var violations []string
for _, item := range []struct{ name, value string }{{"serverInstanceId", value.ServerInstanceID}, {"runBindingId", value.RunBindingID}, {"runEndpointId", value.RunEndpointID}, {"pluginId", value.PluginID}, {"pluginVersion", value.PluginVersion}, {"adapterVersion", value.AdapterVersion}, {"databaseIdentity", value.DatabaseIdentity}} {
violations = appendRequired(violations, prefix+"."+item.name, item.value)
if containsSCUMProtectedMaterial(item.value) {
violations = append(violations, prefix+"."+item.name+" contains protected connection material")
}
}
if value.GameVersion != "" && containsSCUMProtectedMaterial(value.GameVersion) {
violations = append(violations, prefix+".gameVersion contains protected connection material")
}
return violations
}
func validateSCUMSchemaProbeBounds(prefix string, value domain.SCUMSchemaProbeBounds) []string {
var violations []string
if value.MaxObjects < 1 || value.MaxObjects > maxSCUMProbeObjects {
violations = append(violations, prefix+".maxObjects is out of bounds")
}
if value.MaxColumnsPerObject < 1 || value.MaxColumnsPerObject > maxSCUMProbeColumnsPerObject {
violations = append(violations, prefix+".maxColumnsPerObject is out of bounds")
}
if value.MaxIndexesPerObject < 0 || value.MaxIndexesPerObject > maxSCUMProbeIndexesPerObject {
violations = append(violations, prefix+".maxIndexesPerObject is out of bounds")
}
if value.MaxForeignKeys < 0 || value.MaxForeignKeys > maxSCUMProbeForeignKeys {
violations = append(violations, prefix+".maxForeignKeys is out of bounds")
}
if value.MaxCardinalityReads < 0 || value.MaxCardinalityReads > maxSCUMProbeObjects {
violations = append(violations, prefix+".maxCardinalityReads is out of bounds")
}
if value.MaxSampleRows < 0 || value.MaxSampleRows > maxSCUMProbeSamples {
violations = append(violations, prefix+".maxSampleRows is out of bounds")
}
if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS {
violations = append(violations, prefix+".timeoutMs is out of bounds")
}
if value.MaxResultBytes < 1 || value.MaxResultBytes > maxSCUMProbeResultBytes {
violations = append(violations, prefix+".maxResultBytes is out of bounds")
}
return violations
}
func validateSCUMSQLiteTemplateBounds(prefix string, value domain.SCUMSQLiteTemplateBounds) []string {
var violations []string
if value.MaxParameters < 0 || value.MaxParameters > maxSCUMTemplateParameters {
violations = append(violations, prefix+".maxParameters is out of bounds")
}
if value.MaxRows < 1 || value.MaxRows > maxSCUMTemplateRows {
violations = append(violations, prefix+".maxRows is out of bounds")
}
if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS {
violations = append(violations, prefix+".timeoutMs is out of bounds")
}
if value.BusyTimeoutMS < 0 || value.BusyTimeoutMS > maxSCUMTemplateBusyTimeoutMS {
violations = append(violations, prefix+".busyTimeoutMs is out of bounds")
}
if value.MaxResultBytes < 1 || value.MaxResultBytes > maxSCUMProbeResultBytes {
violations = append(violations, prefix+".maxResultBytes is out of bounds")
}
return violations
}
func validateSCUMTypedRCONTemplateBounds(prefix string, value domain.SCUMTypedRCONTemplateBounds) []string {
var violations []string
if value.MaxPayloadBytes < 1 || value.MaxPayloadBytes > maxSCUMRCONPayloadBytes {
violations = append(violations, prefix+".maxPayloadBytes is out of bounds")
}
if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS {
violations = append(violations, prefix+".timeoutMs is out of bounds")
}
if value.MaxResponseBytes < 1 || value.MaxResponseBytes > maxSCUMRCONResponseBytes {
violations = append(violations, prefix+".maxResponseBytes is out of bounds")
}
if value.MaxConfirmRecords < 1 || value.MaxConfirmRecords > maxSCUMRCONConfirmRecords {
violations = append(violations, prefix+".maxConfirmRecords is out of bounds")
}
return violations
}
func validateSCUMGuardedMutationBounds(prefix string, value domain.SCUMGuardedMutationBounds) []string {
var violations []string
if value.MaxPayloadBytes < 1 || value.MaxPayloadBytes > maxSCUMMutationPayloadBytes {
violations = append(violations, prefix+".maxPayloadBytes is out of bounds")
}
if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS {
violations = append(violations, prefix+".timeoutMs is out of bounds")
}
if value.BusyTimeoutMS < 0 || value.BusyTimeoutMS > maxSCUMTemplateBusyTimeoutMS {
violations = append(violations, prefix+".busyTimeoutMs is out of bounds")
}
if value.MaxReadbackBytes < 1 || value.MaxReadbackBytes > maxSCUMMutationReadbackBytes {
violations = append(violations, prefix+".maxReadbackBytes is out of bounds")
}
if value.MaxAffectedRows != 1 {
violations = append(violations, prefix+".maxAffectedRows must be 1")
}
return violations
}
func validateSCUMParsedLogBatchBounds(prefix string, value domain.SCUMParsedLogBatchBounds) []string {
var violations []string
if value.MaxEvents < 0 || value.MaxEvents > maxSCUMParsedLogEvents {
violations = append(violations, prefix+".maxEvents is out of bounds")
}
if value.MaxPayloadBytes < 1 || value.MaxPayloadBytes > maxSCUMParsedLogPayloadBytes {
violations = append(violations, prefix+".maxPayloadBytes is out of bounds")
}
if value.MaxLineBytes < 1 || value.MaxLineBytes > maxSCUMParsedLogLineBytes {
violations = append(violations, prefix+".maxLineBytes is out of bounds")
}
if value.MaxResultBytes < 1 || value.MaxResultBytes > maxSCUMParsedLogResultBytes {
violations = append(violations, prefix+".maxResultBytes is out of bounds")
}
return violations
}
func validateSCUMParsedLogCursor(prefix string, value domain.SCUMParsedLogCursor) []string {
var violations []string
if !validSCUMFingerprint(value.SourceIdentityDigest) || containsSCUMProtectedMaterial(value.SourceIdentityDigest) || containsSCUMNetworkMaterial(value.SourceIdentityDigest) {
violations = append(violations, prefix+".sourceIdentityDigest must be a redacted fingerprint")
}
if !validSCUMFingerprint(value.StreamGeneration) || containsSCUMProtectedMaterial(value.StreamGeneration) || containsSCUMNetworkMaterial(value.StreamGeneration) {
violations = append(violations, prefix+".streamGeneration must be a redacted fingerprint")
}
return violations
}
func validateSCUMParsedLogEvent(prefix string, event domain.SCUMParsedLogEvent, result domain.SCUMParsedLogBatchResult) []string {
var violations []string
violations = append(violations, validateSCUMTemplateKey(prefix+".eventType", event.EventType)...)
violations = append(violations, validateSCUMParsedLogCursor(prefix+".cursor", event.Cursor)...)
for _, item := range []struct{ name, value string }{{"logicalEventDigest", event.LogicalEventDigest}, {"eventDigest", event.EventDigest}, {"payloadDigest", event.PayloadDigest}} {
if !validSCUMDigest(item.value) {
violations = append(violations, prefix+"."+item.name+" must be sha256 digest")
}
}
if result.EventCount > 0 && event.Cursor.Sequence < result.FirstCursor.Sequence || result.EventCount > 0 && event.Cursor.Sequence > result.LastCursor.Sequence {
violations = append(violations, prefix+".cursor.sequence is outside batch range")
}
if event.Cursor.SourceIdentityDigest != result.FirstCursor.SourceIdentityDigest || event.Cursor.StreamGeneration != result.FirstCursor.StreamGeneration {
violations = append(violations, prefix+".cursor must match batch source identity and generation")
}
violations = append(violations, validateSCUMValueMap(prefix+".payload", event.Payload, 64)...)
violations = append(violations, validateSCUMJSONSize(prefix+".payload", event.Payload, result.Limits.MaxPayloadBytes)...)
for key, value := range event.Payload {
if strings.Contains(strings.ToLower(key), "raw") || containsSCUMProtectedMaterial(key) || containsSCUMNetworkMaterial(key) {
violations = append(violations, prefix+".payload key is unsafe")
}
if text, ok := value.(string); ok && (containsSCUMProtectedMaterial(text) || containsSCUMNetworkMaterial(text) || containsSCUMRawXML(text)) {
violations = append(violations, prefix+".payload value is unsafe")
}
}
return violations
}
func validateSCUMTemplateKey(prefix, value string) []string {
var violations []string
violations = appendRequired(violations, prefix, value)
if value != "" && (!scumTemplateKeyPattern.MatchString(value) || strings.Contains(value, "..")) {
violations = append(violations, prefix+" is unsafe")
}
return violations
}
func validateSCUMValueMap(prefix string, values map[string]any, maxItems int) []string {
var violations []string
if maxItems >= 0 && len(values) > maxItems {
violations = append(violations, prefix+" exceeds declared limit")
}
for key, value := range values {
loweredKey := strings.ToLower(key)
if !scumTemplateKeyPattern.MatchString(key) || containsSCUMProtectedMaterial(key) || strings.Contains(key, "..") || strings.Contains(loweredKey, "command") || strings.Contains(loweredKey, "rcon") {
violations = append(violations, prefix+" key is unsafe")
}
field := prefix + ".value"
violations = append(violations, validateSCUMValue(field, value)...)
}
return violations
}
func validateSCUMGuardedMutationPayload(prefix string, values map[string]any) []string {
violations := validateSCUMValueMap(prefix, values, 64)
for key, value := range values {
loweredKey := strings.ToLower(key)
if strings.Contains(loweredKey, "sql") || strings.Contains(loweredKey, "xml") || strings.Contains(loweredKey, "path") || strings.Contains(loweredKey, "table") || strings.Contains(loweredKey, "column") || strings.Contains(loweredKey, "query") || strings.Contains(loweredKey, "raw") || strings.Contains(loweredKey, "855") {
violations = append(violations, prefix+" key is unsafe")
}
if text, ok := value.(string); ok {
trimmed := strings.TrimSpace(strings.ToLower(text))
if containsSCUMRawXML(text) || trimmed == "855" || strings.Contains(trimmed, "fieldkey=855") || strings.Contains(trimmed, "prisoner.value") {
violations = append(violations, prefix+" value is unsafe")
}
}
}
return violations
}
func validateSCUMValue(prefix string, value any) []string {
var violations []string
switch item := value.(type) {
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
return nil
case string:
if len([]byte(item)) > maxSCUMTemplateValueBytes {
violations = append(violations, prefix+" is too large")
}
if containsSCUMProtectedMaterial(item) {
violations = append(violations, prefix+" contains protected material")
}
default:
violations = append(violations, prefix+" must be a scalar value")
}
return violations
}
func validateSCUMJSONSize(prefix string, value any, maxBytes int) []string {
if maxBytes <= 0 {
return []string{prefix + " max byte limit is required"}
}
payload, err := json.Marshal(value)
if err != nil {
return []string{prefix + " must be JSON serializable"}
}
if len(payload) > maxBytes {
return []string{prefix + " exceeds declared byte limit"}
}
return nil
}
func validateSCUMSchemaObjectEvidence(prefix string, value domain.SCUMSchemaObjectEvidence) []string {
var violations []string
if !validSCUMFingerprint(value.ObjectHash) {
violations = append(violations, prefix+".objectHash must be a digest/fingerprint")
}
if value.Kind != "table" && value.Kind != "view" && value.Kind != "index" && value.Kind != "trigger" {
violations = append(violations, prefix+".kind is invalid")
}
if !validSCUMFingerprint(value.NameFingerprint) || containsSCUMProtectedMaterial(value.NameFingerprint) {
violations = append(violations, prefix+".nameFingerprint must be redacted")
}
for i, column := range value.DeclaredColumns {
field := fmt.Sprintf("%s.declaredColumns[%d]", prefix, i)
if !validSCUMFingerprint(column.NameFingerprint) || containsSCUMProtectedMaterial(column.NameFingerprint) {
violations = append(violations, field+".nameFingerprint must be redacted")
}
if containsSCUMProtectedMaterial(column.DeclaredType) {
violations = append(violations, field+".declaredType contains protected material")
}
}
for i, index := range value.Indexes {
field := fmt.Sprintf("%s.indexes[%d]", prefix, i)
if !validSCUMFingerprint(index.NameFingerprint) || containsSCUMProtectedMaterial(index.NameFingerprint) {
violations = append(violations, field+".nameFingerprint must be redacted")
}
for columnIndex, hash := range index.ColumnHashes {
if !validSCUMFingerprint(hash) {
violations = append(violations, fmt.Sprintf("%s.columnHashes[%d] must be a digest/fingerprint", field, columnIndex))
}
}
}
for i, fk := range value.ForeignKeys {
field := fmt.Sprintf("%s.foreignKeys[%d]", prefix, i)
for _, item := range []struct{ name, value string }{{"fromColumnHash", fk.FromColumnHash}, {"toObjectHash", fk.ToObjectHash}, {"toColumnHash", fk.ToColumnHash}} {
if !validSCUMFingerprint(item.value) {
violations = append(violations, field+"."+item.name+" must be a digest/fingerprint")
}
}
}
for i, sample := range value.SampleFingerprints {
if !validSCUMFingerprint(sample) || containsSCUMProtectedMaterial(sample) {
violations = append(violations, fmt.Sprintf("%s.sampleFingerprints[%d] must be a redacted fingerprint", prefix, i))
}
}
return violations
}
func validateSCUMSafeError(prefix string, value domain.SCUMSafeError) []string {
var violations []string
if !validSCUMSafeErrorCode(value.Code) {
violations = append(violations, prefix+".code is invalid")
}
if containsSCUMProtectedMaterial(value.Message) {
violations = append(violations, prefix+".message contains protected material")
}
if len(value.Message) > 320 {
violations = append(violations, prefix+".message is too long")
}
return violations
}
func validSCUMDataCapability(value domain.SCUMDataCapability) bool {
switch value {
case domain.SCUMDataCapabilitySchemaProbe, domain.SCUMDataCapabilityPlayerRead, domain.SCUMDataCapabilityPlayerDetailRead, domain.SCUMDataCapabilitySquadRead, domain.SCUMDataCapabilitySquadMemberRead, domain.SCUMDataCapabilityVehicleRead, domain.SCUMDataCapabilityFlagRead, domain.SCUMDataCapabilityPositionRead, domain.SCUMDataCapabilityProfileXMLWrite, domain.SCUMDataCapabilityEconomyCommand, domain.SCUMDataCapabilityGiftCommand:
return true
default:
return false
}
}
func validSCUMReadCapability(value domain.SCUMDataCapability) bool {
switch value {
case domain.SCUMDataCapabilityPlayerRead, domain.SCUMDataCapabilityPlayerDetailRead, domain.SCUMDataCapabilitySquadRead, domain.SCUMDataCapabilitySquadMemberRead, domain.SCUMDataCapabilityVehicleRead, domain.SCUMDataCapabilityFlagRead, domain.SCUMDataCapabilityPositionRead:
return true
default:
return false
}
}
func validSCUMRCONWriteCapability(value domain.SCUMDataCapability) bool {
switch value {
case domain.SCUMDataCapabilityEconomyCommand, domain.SCUMDataCapabilityGiftCommand:
return true
default:
return false
}
}
func validSCUMGuardedMutationCapability(value domain.SCUMDataCapability) bool {
switch value {
case domain.SCUMDataCapabilityProfileXMLWrite:
return true
default:
return false
}
}
func validSCUMTerminalResultStatus(value domain.SCUMTerminalResultStatus) bool {
switch value {
case domain.SCUMTerminalResultSucceeded, domain.SCUMTerminalResultFailed, domain.SCUMTerminalResultCancelled:
return true
default:
return false
}
}
func validSCUMRCONConfirmationStatus(value domain.SCUMRCONConfirmationStatus) bool {
switch value {
case domain.SCUMRCONConfirmationConfirmed, domain.SCUMRCONConfirmationFailed, domain.SCUMRCONConfirmationUnknown:
return true
default:
return false
}
}
func validSCUMMutationReadbackStatus(value domain.SCUMMutationReadbackStatus) bool {
switch value {
case domain.SCUMMutationReadbackConfirmed, domain.SCUMMutationReadbackFailed, domain.SCUMMutationReadbackConflict, domain.SCUMMutationReadbackUnknown:
return true
default:
return false
}
}
func validSCUMLogTailState(value domain.SCUMLogTailState) bool {
switch value {
case domain.SCUMLogTailAdvanced, domain.SCUMLogTailRotated, domain.SCUMLogTailTruncated, domain.SCUMLogTailRestarted, domain.SCUMLogTailPartial, domain.SCUMLogTailReplayed:
return true
default:
return false
}
}
func validSCUMCapabilityEvidenceStatus(value domain.SCUMCapabilityEvidenceStatus) bool {
switch value {
case domain.SCUMCapabilityEvidenceMissing, domain.SCUMCapabilityEvidenceCompatible, domain.SCUMCapabilityEvidenceIncompatible, domain.SCUMCapabilityEvidenceFailed:
return true
default:
return false
}
}
func validSCUMSchemaProbeResultStatus(value domain.SCUMCapabilityEvidenceStatus) bool {
switch value {
case domain.SCUMSchemaProbeStatusSucceeded, domain.SCUMCapabilityEvidenceCompatible, domain.SCUMCapabilityEvidenceFailed:
return true
default:
return false
}
}
func validSCUMSafeErrorCode(value domain.SCUMSafeErrorCode) bool {
switch value {
case "", domain.SCUMSafeErrorNone, domain.SCUMSafeErrorProbeExecutorAbsent, domain.SCUMSafeErrorProbeMissing, domain.SCUMSafeErrorProbeFailed, domain.SCUMSafeErrorSchemaIncompatible, domain.SCUMSafeErrorBindingMismatch, domain.SCUMSafeErrorAdapterMismatch, domain.SCUMSafeErrorFingerprintMismatch, domain.SCUMSafeErrorDigestMismatch, domain.SCUMSafeErrorEvidenceExpired, domain.SCUMSafeErrorInvalidProbePayload, domain.SCUMSafeErrorInvalidRequest, domain.SCUMSafeErrorTargetUnavailable, domain.SCUMSafeErrorSourceUnavailable, domain.SCUMSafeErrorSQLiteOpenFailed, domain.SCUMSafeErrorSQLiteReadFailed, domain.SCUMSafeErrorDatabaseBusy, domain.SCUMSafeErrorTimeout, domain.SCUMSafeErrorCancelled, domain.SCUMSafeErrorSourceChanged, domain.SCUMSafeErrorResultLimitExceeded, domain.SCUMSafeErrorTemplateMissing, domain.SCUMSafeErrorTemplateMismatch, domain.SCUMSafeErrorParameterInvalid, domain.SCUMSafeErrorRowLimitExceeded, domain.SCUMSafeErrorResultSchemaInvalid, domain.SCUMSafeErrorMutationGuardMismatch, domain.SCUMSafeErrorMutationBackupUnavailable, domain.SCUMSafeErrorMutationOfflineRequired, domain.SCUMSafeErrorMutationConfirmationMissing, domain.SCUMSafeErrorMutationPatchInvalid, domain.SCUMSafeErrorAffectedRowsMismatch, domain.SCUMSafeErrorReadbackMismatch, domain.SCUMSafeErrorRollbackFailed:
return true
default:
return scumDataTargetSafeErrorPattern.MatchString(string(value))
}
}
func containsSCUMRawXML(value string) bool {
return regexp.MustCompile(`<\s*/?\s*[A-Za-z][^>]*>`).MatchString(value)
}
func validSCUMFingerprint(value string) bool { return scumHashPattern.MatchString(value) }
func validSCUMDigest(value string) bool {
return regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$`).MatchString(value)
}
func containsSCUMProtectedMaterial(value string) bool {
trimmed := strings.TrimSpace(value)
lowered := strings.ToLower(trimmed)
if trimmed == "" {
return false
}
if strings.Contains(lowered, "select ") || strings.Contains(lowered, "insert into") || strings.Contains(lowered, "update ") || strings.Contains(lowered, "delete from") || strings.Contains(lowered, "pragma ") || strings.Contains(lowered, "attach database") {
return true
}
if strings.Contains(lowered, "dsn") || strings.Contains(lowered, "password") || strings.Contains(lowered, "credential") || strings.Contains(lowered, "token") || strings.Contains(lowered, "socket") || strings.Contains(lowered, "rcon") {
return true
}
if strings.HasPrefix(lowered, "sqlite://") || strings.HasPrefix(lowered, "mysql://") || strings.HasPrefix(lowered, "file://") || strings.HasPrefix(lowered, "tcp://") || strings.HasPrefix(lowered, "unix://") {
return true
}
if strings.HasPrefix(trimmed, "/") || strings.HasPrefix(trimmed, "\\\\") || regexp.MustCompile(`[A-Za-z]:[\\/]`).MatchString(trimmed) {
return true
}
return false
}
func containsSCUMNetworkMaterial(value string) bool {
trimmed := strings.TrimSpace(value)
lowered := strings.ToLower(trimmed)
if trimmed == "" {
return false
}
if strings.Contains(lowered, "ip=") || strings.Contains(lowered, "addr=") || strings.Contains(lowered, "endpoint=") || strings.Contains(lowered, "port=") {
return true
}
if regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`).MatchString(trimmed) {
return true
}
for _, token := range regexp.MustCompile(`[\s,;()\[\]{}'"]+`).Split(trimmed, -1) {
if strings.Contains(token, ":") && net.ParseIP(strings.Trim(token, "<>")) != nil {
return true
}
}
return false
}
-309
View File
@@ -1,309 +0,0 @@
package validator
import (
"strings"
"testing"
"time"
"browser.local/platform/domain"
)
const scumProbeHash = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
func TestValidateSCUMSchemaProbeRequestAllowsBoundedGenericProbe(t *testing.T) {
request := domain.SCUMSchemaProbeRequest{RequestID: "probe-1", JobID: "job-1", Binding: validatorSCUMBinding(), Bounds: domain.DefaultSCUMSchemaProbeBounds(), RequestedAt: time.Now()}
if err := ValidateSCUMSchemaProbeRequest(request); err != nil {
t.Fatalf("expected valid probe request, got %v", err)
}
}
func TestValidateSCUMSchemaProbeRequestRejectsHostPathsAndLooseBounds(t *testing.T) {
request := domain.SCUMSchemaProbeRequest{RequestID: "probe-1", JobID: "job-1", Binding: validatorSCUMBinding(), Bounds: domain.DefaultSCUMSchemaProbeBounds()}
request.Binding.DatabaseIdentity = `C:\SCUM\Saved\SaveFiles\SCUM.db`
request.Bounds.MaxSampleRows = 25
err := ValidateSCUMSchemaProbeRequest(request)
if err == nil || !strings.Contains(err.Error(), "protected connection material") || !strings.Contains(err.Error(), "maxSampleRows") {
t.Fatalf("expected protected material and bound violations, got %v", err)
}
}
func TestValidateSCUMSchemaProbeResultRejectsRawSQLAndRows(t *testing.T) {
result := domain.SCUMSchemaProbeResult{
RequestID: "probe-1",
JobID: "job-1",
Binding: validatorSCUMBinding(),
Status: domain.SCUMCapabilityEvidenceCompatible,
SchemaFingerprint: scumProbeHash,
ObservedAt: time.Now(),
ResultDigest: scumProbeHash,
Limits: domain.DefaultSCUMSchemaProbeBounds(),
Objects: []domain.SCUMSchemaObjectEvidence{{
ObjectHash: scumProbeHash,
Kind: "table",
NameFingerprint: "select * from players",
DeclaredColumns: []domain.SCUMSchemaColumnEvidence{{NameFingerprint: scumProbeHash, DeclaredType: "TEXT", Ordinal: 1}},
SampleFingerprints: []string{"{\"raw\":\"row\"}"},
}},
}
err := ValidateSCUMSchemaProbeResult(result)
if err == nil || !strings.Contains(err.Error(), "nameFingerprint must be redacted") || !strings.Contains(err.Error(), "sampleFingerprints") {
t.Fatalf("expected redaction violations, got %v", err)
}
}
func TestValidateSCUMSchemaProbeResultAcceptsRunTerminalStatuses(t *testing.T) {
succeeded := domain.SCUMSchemaProbeResult{RequestID: "probe-1", JobID: "job-1", Binding: validatorSCUMBinding(), Status: domain.SCUMSchemaProbeStatusSucceeded, SourceFingerprint: scumProbeHash, SchemaFingerprint: scumProbeHash, ObservedAt: time.Now(), ResultDigest: scumProbeHash, Limits: domain.DefaultSCUMSchemaProbeBounds(), SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
if err := ValidateSCUMSchemaProbeResult(succeeded); err != nil {
t.Fatalf("expected succeeded Run probe result to validate, got %v", err)
}
succeeded.SourceFingerprint = "C:/db/SCUM.db"
if err := ValidateSCUMSchemaProbeResult(succeeded); err == nil || !strings.Contains(err.Error(), "sourceFingerprint must be a digest/fingerprint") {
t.Fatalf("expected raw source fingerprint rejection, got %v", err)
}
succeeded.SourceFingerprint = scumProbeHash
failed := succeeded
failed.Status = domain.SCUMCapabilityEvidenceFailed
failed.SchemaFingerprint = ""
failed.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorTargetUnavailable, Retryable: false}
if err := ValidateSCUMSchemaProbeResult(failed); err != nil {
t.Fatalf("expected safe failed Run probe result to validate, got %v", err)
}
failed.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode("data_target_plan_invalid"), Retryable: false}
if err := ValidateSCUMSchemaProbeResult(failed); err != nil {
t.Fatalf("expected generic Run data-target failure to validate, got %v", err)
}
failed.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode("data_target_invalid path"), Retryable: false}
if err := ValidateSCUMSchemaProbeResult(failed); err == nil || !strings.Contains(err.Error(), "safeError.code is invalid") {
t.Fatalf("expected unsafe data-target code rejection, got %v", err)
}
}
func TestValidateSCUMSQLiteTemplateRequestAllowsBoundedGenericTemplate(t *testing.T) {
request := validatorSCUMSQLiteTemplateRequest()
if err := ValidateSCUMSQLiteTemplateRequest(request); err != nil {
t.Fatalf("expected valid SQLite template request, got %v", err)
}
}
func TestValidateSCUMSQLiteTemplateRequestRejectsSQLPathsAndLooseBounds(t *testing.T) {
request := validatorSCUMSQLiteTemplateRequest()
request.TemplateKey = "select * from players"
request.Parameters = map[string]any{"profilePath": `C:\SCUM\Saved\SCUM.db`}
request.Bounds.MaxRows = 50000
err := ValidateSCUMSQLiteTemplateRequest(request)
if err == nil || !strings.Contains(err.Error(), "templateKey is unsafe") || !strings.Contains(err.Error(), "protected material") || !strings.Contains(err.Error(), "maxRows") {
t.Fatalf("expected template/key/bounds violations, got %v", err)
}
}
func TestValidateSCUMSQLiteTemplateResultAcceptsTypedRows(t *testing.T) {
result := validatorSCUMSQLiteTemplateResult()
if err := ValidateSCUMSQLiteTemplateResult(result); err != nil {
t.Fatalf("expected valid SQLite template result, got %v", err)
}
}
func TestValidateSCUMSQLiteTemplateResultRejectsUnsafeRowsAndMismatchedCounts(t *testing.T) {
result := validatorSCUMSQLiteTemplateResult()
result.RowCount = 2
result.Rows[0]["displayName"] = "select * from user_profile"
err := ValidateSCUMSQLiteTemplateResult(result)
if err == nil || !strings.Contains(err.Error(), "rowCount") || !strings.Contains(err.Error(), "protected material") {
t.Fatalf("expected row-count and protected-row violations, got %v", err)
}
}
func TestValidateSCUMTypedRCONTemplateRequestAllowsBoundedGenericTemplate(t *testing.T) {
request := validatorSCUMTypedRCONTemplateRequest()
if err := ValidateSCUMTypedRCONTemplateRequest(request); err != nil {
t.Fatalf("expected valid typed RCON template request, got %v", err)
}
}
func TestValidateSCUMTypedRCONTemplateRequestRejectsRawCommandAndLooseBounds(t *testing.T) {
request := validatorSCUMTypedRCONTemplateRequest()
request.Payload = map[string]any{"rawCommand": "#SetFamePoints 7 100"}
request.ReviewReason = `use C:\SCUM\secret.txt`
request.Bounds.MaxPayloadBytes = 100000
err := ValidateSCUMTypedRCONTemplateRequest(request)
if err == nil || !strings.Contains(err.Error(), "payload key is unsafe") || !strings.Contains(err.Error(), "reviewReason is unsafe") || !strings.Contains(err.Error(), "maxPayloadBytes") {
t.Fatalf("expected raw command/reason/bounds violations, got %v", err)
}
}
func TestValidateSCUMTypedRCONTemplateResultAcceptsConfirmedEnvelope(t *testing.T) {
result := validatorSCUMTypedRCONTemplateResult()
if err := ValidateSCUMTypedRCONTemplateResult(result); err != nil {
t.Fatalf("expected valid typed RCON template result, got %v", err)
}
}
func TestValidateSCUMTypedRCONTemplateResultRejectsUnconfirmedSuccessAndUnsafeSummary(t *testing.T) {
result := validatorSCUMTypedRCONTemplateResult()
result.ConfirmationStatus = domain.SCUMRCONConfirmationUnknown
result.SafeSummary = "rcon password leaked"
err := ValidateSCUMTypedRCONTemplateResult(result)
if err == nil || !strings.Contains(err.Error(), "confirmed") || !strings.Contains(err.Error(), "safeSummary") {
t.Fatalf("expected confirmation and summary violations, got %v", err)
}
}
func TestValidateSCUMGuardedMutationRequestAllowsBoundedGenericTemplate(t *testing.T) {
request := validatorSCUMGuardedMutationRequest()
if err := ValidateSCUMGuardedMutationRequest(request); err != nil {
t.Fatalf("expected valid guarded mutation request, got %v", err)
}
}
func TestValidateSCUMGuardedMutationRequestRejectsRawXMLSQL855AndMissingGuards(t *testing.T) {
request := validatorSCUMGuardedMutationRequest()
request.Payload = map[string]any{"rawXml": "<CharacterTemplate><Attribute name=\"Strength\" value=\"9\" /></CharacterTemplate>", "fieldKey855": "855"}
request.BackupEvidenceDigest = ""
request.OfflineEvidenceDigest = ""
request.DangerConfirmationDigest = ""
request.ReadbackExpectationDigest = ""
request.ReviewReason = "update sqlite:///private/tmp/SCUM.db directly"
request.Bounds.MaxAffectedRows = 2
err := ValidateSCUMGuardedMutationRequest(request)
if err == nil || !strings.Contains(err.Error(), "payload key is unsafe") || !strings.Contains(err.Error(), "payload value is unsafe") || !strings.Contains(err.Error(), "backupEvidenceDigest") || !strings.Contains(err.Error(), "offlineEvidenceDigest") || !strings.Contains(err.Error(), "dangerConfirmationDigest") || !strings.Contains(err.Error(), "readbackExpectationDigest") || !strings.Contains(err.Error(), "maxAffectedRows") || !strings.Contains(err.Error(), "reviewReason") {
t.Fatalf("expected guarded mutation safety violations, got %v", err)
}
}
func TestValidateSCUMGuardedMutationResultAcceptsConfirmedSingleRowEnvelope(t *testing.T) {
result := validatorSCUMGuardedMutationResult()
if err := ValidateSCUMGuardedMutationResult(result); err != nil {
t.Fatalf("expected valid guarded mutation result, got %v", err)
}
}
func TestValidateSCUMGuardedMutationResultRejectsMultiRowMissingReadbackAndUnsafeSummary(t *testing.T) {
result := validatorSCUMGuardedMutationResult()
result.AffectedRows = 2
result.ReadbackStatus = domain.SCUMMutationReadbackUnknown
result.ReadbackDigest = ""
result.SafeSummary = "raw <CharacterTemplate /> leaked"
err := ValidateSCUMGuardedMutationResult(result)
if err == nil || !strings.Contains(err.Error(), "exactly one affected row") || !strings.Contains(err.Error(), "readback") || !strings.Contains(err.Error(), "safeSummary") {
t.Fatalf("expected affected-row/readback/summary violations, got %v", err)
}
}
func TestValidateSCUMParsedLogBatchResultAcceptsRotationSafeEnvelope(t *testing.T) {
result := validatorSCUMParsedLogBatchResult()
if err := ValidateSCUMParsedLogBatchResult(result); err != nil {
t.Fatalf("expected valid parsed log batch result, got %v", err)
}
}
func TestValidateSCUMParsedLogBatchResultRejectsRawLineNetworkMaterialAndLooseBounds(t *testing.T) {
result := validatorSCUMParsedLogBatchResult()
result.ParserDigest = ""
result.FirstCursor.SourceIdentityDigest = `C:\SCUM\Saved\Logs\login.log`
result.Events[0].Payload = map[string]any{"rawLine": "2026.08.13: '203.0.113.10 player' logged in"}
result.Limits.MaxLineBytes = 1000000
err := ValidateSCUMParsedLogBatchResult(result)
if err == nil || !strings.Contains(err.Error(), "parserDigest") || !strings.Contains(err.Error(), "sourceIdentityDigest") || !strings.Contains(err.Error(), "payload key is unsafe") || !strings.Contains(err.Error(), "payload value is unsafe") || !strings.Contains(err.Error(), "maxLineBytes") {
t.Fatalf("expected parsed-log safety violations, got %v", err)
}
}
func TestValidateSCUMCapabilityEvidenceRequiresSafeCurrentServiceIdentity(t *testing.T) {
evidence := domain.SCUMCapabilityEvidence{Capability: domain.SCUMDataCapabilityPlayerRead, Status: domain.SCUMCapabilityEvidenceCompatible, Binding: validatorSCUMBinding(), AdapterVersion: "adapter-1", SchemaFingerprint: scumProbeHash, ProbeResultDigest: scumProbeHash, AssetDigests: []string{scumProbeHash}, SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
if err := ValidateSCUMCapabilityEvidence(evidence); err != nil {
t.Fatalf("expected valid evidence, got %v", err)
}
evidence.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorProbeFailed, Message: "sqlite:///private/tmp/SCUM.db locked"}
if err := ValidateSCUMCapabilityEvidence(evidence); err == nil || !strings.Contains(err.Error(), "protected material") {
t.Fatalf("expected safe error redaction violation, got %v", err)
}
}
func validatorSCUMBinding() domain.SCUMBindingIdentity {
return domain.SCUMBindingIdentity{ServerInstanceID: "server-1", RunBindingID: "binding-1", RunEndpointID: "run-1", PluginID: "game.scum", PluginVersion: "0.1.6", AdapterVersion: "adapter-1", GameVersion: "scum-1", DatabaseIdentity: "db-fingerprint-1"}
}
func validatorSCUMSQLiteTemplateRequest() domain.SCUMSQLiteTemplateRequest {
return domain.SCUMSQLiteTemplateRequest{RequestID: "request-1", JobID: "job-1", Binding: validatorSCUMBinding(), Capability: domain.SCUMDataCapabilityPlayerRead, TargetKey: "scum-database", TemplateKey: "players.active.v1", AdapterVersion: "adapter-1", RequiredSchemaFingerprint: scumProbeHash, AssetDigest: scumProbeHash, ParameterDigest: scumProbeHash, Parameters: map[string]any{"cursor": "", "limit": 100.0}, Bounds: domain.DefaultSCUMSQLiteTemplateBounds(), RequestedAt: time.Now()}
}
func validatorSCUMSQLiteTemplateResult() domain.SCUMSQLiteTemplateResult {
request := validatorSCUMSQLiteTemplateRequest()
return domain.SCUMSQLiteTemplateResult{RequestID: request.RequestID, JobID: request.JobID, Binding: request.Binding, Status: domain.SCUMTerminalResultSucceeded, Capability: request.Capability, TargetKey: request.TargetKey, TemplateKey: request.TemplateKey, AdapterVersion: request.AdapterVersion, SchemaFingerprint: request.RequiredSchemaFingerprint, AssetDigest: request.AssetDigest, ParameterDigest: request.ParameterDigest, SourceFingerprint: scumProbeHash, ObservedAt: time.Now(), ResultDigest: scumProbeHash, RowCount: 1, Rows: []map[string]any{{"externalPlayerId": "player-redacted", "displayName": "Known Player", "fame": 12.5, "online": true, "squadId": nil}}, Limits: request.Bounds, SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
}
func validatorSCUMTypedRCONTemplateRequest() domain.SCUMTypedRCONTemplateRequest {
return domain.SCUMTypedRCONTemplateRequest{RequestID: "request-rcon-1", JobID: "job-rcon-1", Binding: validatorSCUMBinding(), Capability: domain.SCUMDataCapabilityEconomyCommand, TransportKey: "scum-rcon", TargetKey: "scum-rcon", TemplateKey: "economy.fame.set.v1", AdapterVersion: "adapter-1", RequiredSchemaFingerprint: scumProbeHash, AssetDigest: scumProbeHash, PayloadDigest: scumProbeHash, ConfirmationDigest: scumProbeHash, TargetIdentityDigest: scumProbeHash, IdempotencyKey: "idem-rcon-1", Payload: map[string]any{"externalPlayerId": "player-redacted", "absoluteValue": 100.0}, ReviewReason: "operator reviewed absolute fame update", Bounds: domain.DefaultSCUMTypedRCONTemplateBounds(), RequestedAt: time.Now()}
}
func validatorSCUMTypedRCONTemplateResult() domain.SCUMTypedRCONTemplateResult {
request := validatorSCUMTypedRCONTemplateRequest()
return domain.SCUMTypedRCONTemplateResult{RequestID: request.RequestID, JobID: request.JobID, Binding: request.Binding, Status: domain.SCUMTerminalResultSucceeded, Capability: request.Capability, TransportKey: request.TransportKey, TargetKey: request.TargetKey, TemplateKey: request.TemplateKey, AdapterVersion: request.AdapterVersion, SchemaFingerprint: request.RequiredSchemaFingerprint, AssetDigest: request.AssetDigest, PayloadDigest: request.PayloadDigest, ConfirmationDigest: request.ConfirmationDigest, TargetIdentityDigest: request.TargetIdentityDigest, ObservedAt: time.Now(), ResultDigest: scumProbeHash, ResponseDigest: scumProbeHash, ConfirmationStatus: domain.SCUMRCONConfirmationConfirmed, ConfirmationDigestID: scumProbeHash, SafeSummary: "confirmed by declared readback", Limits: request.Bounds, SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
}
func validatorSCUMGuardedMutationRequest() domain.SCUMGuardedMutationRequest {
return domain.SCUMGuardedMutationRequest{RequestID: "request-mutation-1", JobID: "job-mutation-1", Binding: validatorSCUMBinding(), Capability: domain.SCUMDataCapabilityProfileXMLWrite, TargetKey: "scum-mutation-db", TemplateKey: "profile.attributes.patch.v1", AdapterVersion: "adapter-1", RequiredSchemaFingerprint: scumProbeHash, AssetDigest: scumProbeHash, TargetIdentityDigest: scumProbeHash, ExpectedRowDigest: scumProbeHash, ExpectedValueDigest: scumProbeHash, ExpectedXMLDigest: scumProbeHash, PatchDigest: scumProbeHash, BackupEvidenceDigest: scumProbeHash, OfflineEvidenceDigest: scumProbeHash, DangerConfirmationDigest: scumProbeHash, ReadbackExpectationDigest: scumProbeHash, IdempotencyKey: "idem-mutation-1", Payload: map[string]any{"attributeKey": "Strength", "absoluteValue": 8.5}, ReviewReason: "operator confirmed offline profile attribute patch", Bounds: domain.DefaultSCUMGuardedMutationBounds(), RequestedAt: time.Now()}
}
func validatorSCUMGuardedMutationResult() domain.SCUMGuardedMutationResult {
request := validatorSCUMGuardedMutationRequest()
return domain.SCUMGuardedMutationResult{RequestID: request.RequestID, JobID: request.JobID, Binding: request.Binding, Status: domain.SCUMTerminalResultSucceeded, Capability: request.Capability, TargetKey: request.TargetKey, TemplateKey: request.TemplateKey, AdapterVersion: request.AdapterVersion, SchemaFingerprint: request.RequiredSchemaFingerprint, AssetDigest: request.AssetDigest, SourceFingerprint: scumProbeHash, TargetIdentityDigest: request.TargetIdentityDigest, ExpectedRowDigest: request.ExpectedRowDigest, ExpectedValueDigest: request.ExpectedValueDigest, ExpectedXMLDigest: request.ExpectedXMLDigest, PatchDigest: request.PatchDigest, BackupEvidenceDigest: request.BackupEvidenceDigest, OfflineEvidenceDigest: request.OfflineEvidenceDigest, DangerConfirmationDigest: request.DangerConfirmationDigest, ReadbackExpectationDigest: request.ReadbackExpectationDigest, ObservedAt: time.Now(), ResultDigest: scumProbeHash, BeforeDigest: scumProbeHash, AfterDigest: scumProbeHash, ReadbackDigest: scumProbeHash, AffectedRows: 1, ReadbackStatus: domain.SCUMMutationReadbackConfirmed, SafeSummary: "confirmed by declared readback", SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}, Limits: request.Bounds}
}
func validatorSCUMParsedLogBatchResult() domain.SCUMParsedLogBatchResult {
cursor := domain.SCUMParsedLogCursor{SourceIdentityDigest: scumProbeHash, StreamGeneration: scumProbeHash, Sequence: 7}
return domain.SCUMParsedLogBatchResult{
RequestID: "request-log-1",
JobID: "job-log-1",
Binding: validatorSCUMBinding(),
Status: domain.SCUMTerminalResultSucceeded,
SourceKey: "scum-login-events",
StreamKey: "scum.login",
ParserKey: "scum-login-log-login-parser",
ParserVersion: "scum-login-log-v1",
AdapterVersion: "adapter-1",
AssetDigest: scumProbeHash,
ParserDigest: scumProbeHash,
ObservedAt: time.Now(),
ResultDigest: scumProbeHash,
FirstCursor: cursor,
LastCursor: cursor,
TailState: domain.SCUMLogTailRotated,
Replay: true,
EventCount: 1,
SafeSummary: "one sanitized login event parsed from declared source",
SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone},
Limits: domain.DefaultSCUMParsedLogBatchBounds(),
Events: []domain.SCUMParsedLogEvent{{
EventType: "scum.login",
OccurredAt: time.Now(),
Cursor: cursor,
LogicalEventDigest: scumProbeHash,
EventDigest: scumProbeHash,
PayloadDigest: scumProbeHash,
Payload: map[string]any{"externalPlayerId": "player-redacted", "displayName": "Known Player", "profileLocalId": "profile-redacted"},
}},
}
}