Align runtime profiles with plugin-owned records

This commit is contained in:
npc0-hue
2026-09-02 10:22:14 +08:00
parent 6018d8f0fc
commit a027ca70eb
36 changed files with 234 additions and 1590 deletions
+35 -98
View File
@@ -12,10 +12,8 @@ import (
)
var (
runtimeLogEventSchemaRefPattern = regexp.MustCompile(`^[A-Za-z0-9_./-]+\.json$`)
runtimeLogEventTypePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,119}$`)
runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`)
runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`)
runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`)
runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`)
)
func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles) error {
@@ -23,6 +21,9 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
var violations []string
lifecycleKeys := map[string]struct{}{}
transportKeys := map[string]struct{}{}
transportProfiles := map[string]domain.RuntimeTransportProfile{}
dataTargetKeys := map[string]struct{}{}
dataTargetWorkspaces := map[string]struct{}{}
managerKeys := map[string]struct{}{}
dllExtensionKeys := map[string]struct{}{}
dllExtensionStates := map[string]string{}
@@ -31,9 +32,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
installPlanKeys := map[string]struct{}{}
serverDeploymentKeys := map[string]struct{}{}
logSourceKeys := map[string]struct{}{}
logSourceRetentions := map[string]int{}
logEventKeys := map[string]struct{}{}
logEventTypes := map[string]struct{}{}
for i, probe := range profiles.Discovery {
prefix := fmt.Sprintf("runtimeProfiles.discovery[%d]", i)
@@ -237,9 +235,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
prefix := fmt.Sprintf("runtimeProfiles.logSources[%d]", i)
violations = append(violations, validateProfileKey(prefix+".key", source.Key)...)
violations = append(violations, recordRuntimeProfileKey(logSourceKeys, prefix+".key", source.Key)...)
if source.Key != "" {
logSourceRetentions[source.Key] = source.RetentionDays
}
if !oneOf(source.Kind, "process.stdout", "process.stderr", "file.tail", "ftp.poll", "sql.query", "client-manager") {
violations = append(violations, prefix+".kind is invalid")
}
@@ -254,44 +249,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
violations = append(violations, prefix+".retentionDays is invalid")
}
}
if len(profiles.LogEvents) > 128 {
violations = append(violations, "runtimeProfiles.logEvents must not exceed 128")
}
for i, event := range profiles.LogEvents {
prefix := fmt.Sprintf("runtimeProfiles.logEvents[%d]", i)
violations = append(violations, validateProfileKey(prefix+".key", event.Key)...)
violations = append(violations, recordRuntimeProfileKey(logEventKeys, prefix+".key", event.Key)...)
if strings.TrimSpace(event.Title) == "" || len([]rune(event.Title)) > 80 {
violations = append(violations, prefix+".title is invalid")
}
violations = append(violations, validateSafeRuntimeValue(prefix+".title", event.Title)...)
violations = append(violations, validateProfileKey(prefix+".sourceKey", event.SourceKey)...)
if _, exists := logSourceKeys[event.SourceKey]; !exists {
violations = append(violations, prefix+".sourceKey must reference a declared runtime log source")
}
if !runtimeLogEventTypePattern.MatchString(event.EventType) {
violations = append(violations, prefix+".eventType is invalid")
}
violations = append(violations, recordRuntimeProfileKey(logEventTypes, prefix+".eventType", event.EventType)...)
if hasUnsafeRuntimeLogEventSemantics(event.EventType) {
violations = append(violations, prefix+".eventType contains unsafe operation semantics")
}
if !validPluginPermission(event.Permission) {
violations = append(violations, prefix+".permission is not allowed")
}
if len(event.SchemaRef) > 240 || !runtimeLogEventSchemaRefPattern.MatchString(event.SchemaRef) || !safeRelativeJSONRef(event.SchemaRef) {
violations = append(violations, prefix+".schemaRef must be a bounded safe relative JSON reference")
}
if event.RetentionDays < 1 || event.RetentionDays > 365 {
violations = append(violations, prefix+".retentionDays is invalid")
}
if sourceRetention, exists := logSourceRetentions[event.SourceKey]; exists && sourceRetention > 0 && event.RetentionDays > sourceRetention {
violations = append(violations, prefix+".retentionDays must not exceed the source retention")
}
if !oneOf(string(event.Severity), string(domain.RuntimeLogEventSeverityInfo), string(domain.RuntimeLogEventSeverityNotice), string(domain.RuntimeLogEventSeverityWarning), string(domain.RuntimeLogEventSeverityCritical)) {
violations = append(violations, prefix+".severity is invalid")
}
}
for i, transport := range profiles.TransportProfiles {
prefix := fmt.Sprintf("runtimeProfiles.transportProfiles[%d]", i)
violations = append(violations, validateProfileKey(prefix+".key", transport.Key)...)
@@ -311,6 +268,36 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
}
}
violations = append(violations, duplicateViolations(prefix+".capabilities", transport.Capabilities)...)
if transport.Key != "" {
transportProfiles[transport.Key] = transport
}
}
if len(profiles.DataTargets) > 16 {
violations = append(violations, "runtimeProfiles.dataTargets must not exceed 16")
}
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)...)
violations = append(violations, validateProfileKey(prefix+".transportKey", target.TransportKey)...)
violations = append(violations, validateProfileKey(prefix+".sourceRootKey", target.SourceRootKey)...)
violations = append(violations, validateSafeRelativeRuntimePath(prefix+".sourcePath", target.SourcePath)...)
violations = append(violations, validateProfileKey(prefix+".workspaceKey", target.WorkspaceKey)...)
if target.Kind != "sqlite.snapshot" || target.RefreshPolicy != "on-demand-snapshot" || !strings.HasPrefix(target.WorkspaceKey, "databases/") {
violations = append(violations, prefix+" must declare an on-demand sqlite snapshot workspace")
}
if target.MaxBytes < 1 || target.MaxBytes > 1024*1024*1024 {
violations = append(violations, prefix+".maxBytes is invalid")
}
violations = append(violations, validateRuntimePlatforms(prefix+".platforms", target.Platforms)...)
if _, exists := dataTargetWorkspaces[target.WorkspaceKey]; exists {
violations = append(violations, prefix+".workspaceKey is duplicated")
}
dataTargetWorkspaces[target.WorkspaceKey] = struct{}{}
transport, exists := transportProfiles[target.TransportKey]
if !exists || transport.Kind != "sqlite" || transport.TargetKey != target.TransportKey || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery) {
violations = append(violations, prefix+".transportKey must reference a declared SQLite query transport")
}
}
for i, manager := range profiles.ClientManagers {
prefix := fmt.Sprintf("runtimeProfiles.clientManagers[%d]", i)
@@ -609,42 +596,6 @@ func validateSafeRuntimeValue(field, value string) []string {
return nil
}
func hasUnsafeRuntimeLogEventSemantics(eventType string) bool {
lowered := strings.ToLower(strings.TrimSpace(eventType))
tokens := strings.FieldsFunc(lowered, func(char rune) bool {
return char == '.' || char == '_' || char == '-' || char == '/'
})
unsafeTokens := map[string]struct{}{
"apikey": {}, "credential": {}, "credentials": {}, "eval": {}, "exec": {},
"execute": {}, "password": {}, "powershell": {}, "script": {}, "secret": {},
"shell": {}, "socket": {}, "terminal": {}, "token": {},
}
for _, token := range tokens {
if _, unsafe := unsafeTokens[token]; unsafe {
return true
}
}
for index := 0; index+1 < len(tokens); index++ {
pair := tokens[index] + "." + tokens[index+1]
switch pair {
case "absolute.path", "access.key", "api.key", "component.key", "database.query", "direct.socket", "file.path", "host.path", "private.key", "raw.path", "run.direct", "run.socket", "unix.socket":
return true
}
}
tokenSet := make(map[string]struct{}, len(tokens))
for _, token := range tokens {
tokenSet[token] = struct{}{}
}
if _, hasSQL := tokenSet["sql"]; hasSQL {
for _, token := range []string{"query", "statement", "raw"} {
if _, unsafe := tokenSet[token]; unsafe {
return true
}
}
}
return false
}
func recordRuntimeProfileKey(seen map[string]struct{}, field, key string) []string {
if key == "" {
return nil
@@ -681,20 +632,6 @@ func validateRuntimeProfileCapabilityDeclarations(profiles domain.GamePluginRunt
return violations
}
func validateRuntimeLogEventPermissionDeclarations(field string, profiles domain.GamePluginRuntimeProfiles, declared []string) []string {
declaredSet := make(map[string]struct{}, len(declared))
for _, permission := range declared {
declaredSet[permission] = struct{}{}
}
var violations []string
for i, event := range profiles.LogEvents {
if _, exists := declaredSet[event.Permission]; !exists {
violations = append(violations, fmt.Sprintf("%s[%d].permission must be declared by the plugin", field, i))
}
}
return violations
}
func validateLifecycleActionsOptional(actions domain.PluginLifecycleActions) []string {
var violations []string
for field, value := range map[string]string{"install": actions.Install, "start": actions.Start, "stop": actions.Stop, "restart": actions.Restart, "status": actions.Status} {