feat(scum): rebuild plugin-owned management data
This commit is contained in:
@@ -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, validateMapTrajectoryDeclaration("mapTrajectories", plugin.MapTrajectories)...)
|
||||
violations = append(violations, validatePluginCreateFields("createFields", plugin.CreateFields)...)
|
||||
violations = append(violations, validatePluginAssetFiles("lifecycleAssets", plugin.LifecycleAssets)...)
|
||||
violations = append(violations, validateSafePluginStrings("gamePlugin", pluginSafeStrings(plugin))...)
|
||||
@@ -229,7 +228,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, validateMapTrajectoryDeclaration("manifest.mapTrajectories", manifest.MapTrajectories)...)
|
||||
violations = append(violations, validatePluginAssetFileDeclarations("manifest.assetFiles", manifest.AssetFiles)...)
|
||||
violations = append(violations, validatePluginAssetFiles("assetFiles", registration.AssetFiles)...)
|
||||
violations = append(violations, validateRegistrationAssetCoverage(registration.Manifest.AssetFiles, registration.AssetFiles)...)
|
||||
@@ -311,16 +309,6 @@ func validateRegistrationAssetCoverage(declared []domain.PluginAssetFile, payloa
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateMapTrajectoryDeclaration(prefix string, value *domain.GameMapTrajectoryDeclaration) []string {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if value.MapID == "" || value.MapVersion == "" || value.WorldMaxX <= value.WorldMinX || value.WorldMaxY <= value.WorldMinY || value.ImageWidth <= 0 || value.ImageHeight <= 0 || value.Precision <= 0 || value.SampleDistance < 0 || value.SampleIntervalSeconds < 0 || value.RetentionSeconds <= 0 || value.RetentionSeconds > 31*24*60*60 {
|
||||
return []string{prefix + " is invalid"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePluginCreateFields(prefix string, fields []domain.PluginCreateField) []string {
|
||||
if len(fields) > 32 {
|
||||
return []string{prefix + " must contain at most 32 fields"}
|
||||
@@ -439,7 +427,7 @@ func ValidatePluginCreateInputs(fields []domain.PluginCreateField, inputs map[st
|
||||
|
||||
func validateGameClientBridgeManifest(field string, bridge domain.GameClientBridgeManifest, permissions []string, pages []domain.GamePluginPage, runtimeProfiles domain.GamePluginRuntimeProfiles) []string {
|
||||
companionPresent := bridge.Companion != (domain.GameClientBridgeCompanionDeclaration{})
|
||||
if len(bridge.Commands) == 0 && len(bridge.Snapshots) == 0 && len(bridge.QueryTemplates) == 0 && len(bridge.OperationTemplates) == 0 && len(bridge.Pages) == 0 && len(bridge.Features) == 0 && bridge.Retention.KeepForSeconds == 0 && bridge.Retention.MaxRecords == 0 && !companionPresent {
|
||||
if len(bridge.Commands) == 0 && len(bridge.Snapshots) == 0 && len(bridge.QueryTemplates) == 0 && len(bridge.DataPacks) == 0 && len(bridge.OperationTemplates) == 0 && len(bridge.Pages) == 0 && len(bridge.Features) == 0 && bridge.Retention.KeepForSeconds == 0 && bridge.Retention.MaxRecords == 0 && !companionPresent {
|
||||
return nil
|
||||
}
|
||||
var violations []string
|
||||
@@ -578,6 +566,30 @@ func validateGameClientBridgeManifest(field string, bridge domain.GameClientBrid
|
||||
if template.TimeoutSeconds < 1 || template.TimeoutSeconds > 60 {
|
||||
violations = append(violations, prefix+".timeoutSeconds is invalid")
|
||||
}
|
||||
projectsRows := template.SQLRef != "" || template.RowTarget != nil
|
||||
if projectsRows {
|
||||
if !safeRelativeSQLRef(template.SQLRef) {
|
||||
violations = append(violations, prefix+".sqlRef must reference a package-relative SQL asset")
|
||||
}
|
||||
if template.RowTarget == nil {
|
||||
violations = append(violations, prefix+".rowTarget is required for projected queries")
|
||||
} else {
|
||||
target := template.RowTarget
|
||||
if !clientManagerIdentifierPattern.MatchString(target.Collection) || len(target.UpsertKeys) == 0 || len(target.ColumnMappings) == 0 {
|
||||
violations = append(violations, prefix+".rowTarget must declare a collection, upsert keys, and column mappings")
|
||||
}
|
||||
for _, key := range target.UpsertKeys {
|
||||
if !clientManagerIdentifierPattern.MatchString(key) {
|
||||
violations = append(violations, prefix+".rowTarget upsert key is invalid")
|
||||
}
|
||||
}
|
||||
for destination, source := range target.ColumnMappings {
|
||||
if !clientManagerIdentifierPattern.MatchString(destination) || !clientManagerIdentifierPattern.MatchString(source) {
|
||||
violations = append(violations, prefix+".rowTarget column mapping is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transport, exists := transports[template.TransportKey]
|
||||
if !exists {
|
||||
violations = append(violations, prefix+".transportKey must reference a declared runtime transport profile")
|
||||
@@ -590,6 +602,25 @@ func validateGameClientBridgeManifest(field string, bridge domain.GameClientBrid
|
||||
violations = append(violations, prefix+" transport must be sqlite with remote.run.db.sqlite.query capability")
|
||||
}
|
||||
}
|
||||
dataPackKeys := map[string]struct{}{}
|
||||
for index, dataPack := range bridge.DataPacks {
|
||||
prefix := fmt.Sprintf("%s.dataPacks[%d]", field, index)
|
||||
if !validDistributionLogicalKey(dataPack.Key) {
|
||||
violations = append(violations, prefix+".key is invalid")
|
||||
}
|
||||
if _, exists := dataPackKeys[dataPack.Key]; exists {
|
||||
violations = append(violations, prefix+".key is duplicated")
|
||||
}
|
||||
dataPackKeys[dataPack.Key] = struct{}{}
|
||||
if dataPack.DatabaseUserVersion < 1 || len(dataPack.LogParserRefs) == 0 || len(dataPack.ConfigMapRefs) == 0 {
|
||||
violations = append(violations, prefix+" must declare a database version and parser/config assets")
|
||||
}
|
||||
for _, ref := range append(domain.CopyStringSlice(dataPack.LogParserRefs), dataPack.ConfigMapRefs...) {
|
||||
if !safeRelativeJSONRef(ref) {
|
||||
violations = append(violations, prefix+" asset reference is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
operationTemplates := map[string]domain.GameClientBridgeOperationTemplateDeclaration{}
|
||||
for index, template := range bridge.OperationTemplates {
|
||||
prefix := fmt.Sprintf("%s.operationTemplates[%d]", field, index)
|
||||
@@ -2110,6 +2141,15 @@ func safeRelativeJSONRef(value string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func safeRelativeSQLRef(value string) bool {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
lowered := strings.ToLower(trimmed)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "/") || strings.Contains(trimmed, "..") || strings.Contains(trimmed, "://") || strings.Contains(trimmed, `\`) || !strings.HasSuffix(lowered, ".sql") {
|
||||
return false
|
||||
}
|
||||
return len(trimmed) < 2 || trimmed[1] != ':'
|
||||
}
|
||||
|
||||
func looksLikeRawSecret(value string) bool {
|
||||
trimmed := strings.TrimSpace(strings.ToLower(value))
|
||||
if trimmed == "" {
|
||||
|
||||
Reference in New Issue
Block a user