feat(plugin): add SCUM ownership migration foundation

This commit is contained in:
npc0-hue
2026-07-28 18:11:00 +08:00
parent d24074134a
commit 271e1e684f
29 changed files with 328 additions and 315 deletions
+10
View File
@@ -312,10 +312,20 @@ type GamePluginPage struct {
Key string
Title string
Path string
Bundle PluginPageBundle
Permissions []string
BridgeActions []string
}
// PluginPageBundle identifies an installed plugin-owned page module. The host
// validates this declaration before loading a bundle and never selects a game
// page by plugin ID.
type PluginPageBundle struct {
Key string
Version string
IntegritySHA256 string
}
// PluginFileWorkspace is a bounded, logical catalog for a plugin-owned files
// workbench. Keys are logical identifiers, never host paths.
type PluginFileWorkspace struct {
+7
View File
@@ -179,6 +179,9 @@ type GamePluginPageBody struct {
Key string `json:"key"`
Title string `json:"title"`
Path string `json:"path"`
BundleKey string `json:"bundleKey"`
BundleVersion string `json:"bundleVersion"`
BundleIntegritySHA256 string `json:"bundleIntegritySha256"`
Permissions []string `json:"permissions,omitempty"`
BridgeActions []string `json:"bridgeActions,omitempty"`
}
@@ -1880,6 +1883,7 @@ func pagesToDomain(pages []GamePluginPageBody) []domain.GamePluginPage {
Key: page.Key,
Title: page.Title,
Path: page.Path,
Bundle: domain.PluginPageBundle{Key: page.BundleKey, Version: page.BundleVersion, IntegritySHA256: page.BundleIntegritySHA256},
Permissions: domain.CopyStringSlice(page.Permissions),
BridgeActions: domain.CopyStringSlice(page.BridgeActions),
}
@@ -1897,6 +1901,9 @@ func pagesFromDomain(pages []domain.GamePluginPage) []GamePluginPageBody {
Key: page.Key,
Title: page.Title,
Path: page.Path,
BundleKey: page.Bundle.Key,
BundleVersion: page.Bundle.Version,
BundleIntegritySHA256: page.Bundle.IntegritySHA256,
Permissions: domain.CopyStringSlice(page.Permissions),
BridgeActions: domain.CopyStringSlice(page.BridgeActions),
}
+18
View File
@@ -1397,6 +1397,12 @@ func validatePluginPages(pages []domain.GamePluginPage) []string {
if !validPluginPagePath(page.Path) {
violations = append(violations, prefix+".path is invalid")
}
if page.Bundle.Key != "" || page.Bundle.Version != "" || page.Bundle.IntegritySHA256 != "" {
violations = appendRequired(violations, prefix+".bundle.key", page.Bundle.Key)
violations = appendRequired(violations, prefix+".bundle.version", page.Bundle.Version)
violations = appendRequired(violations, prefix+".bundle.integritySha256", page.Bundle.IntegritySHA256)
if !validDistributionLogicalKey(page.Bundle.Key) || !validPluginPageBundleVersion(page.Bundle.Version) || !validPluginPageBundleIntegrity(page.Bundle.IntegritySHA256) { violations = append(violations, prefix+".bundle is invalid") }
}
if page.Key != "" {
if _, exists := seenKeys[page.Key]; exists {
violations = append(violations, prefix+".key duplicates another page")
@@ -1414,6 +1420,18 @@ func validatePluginPages(pages []domain.GamePluginPage) []string {
return violations
}
func validPluginPageBundleVersion(value string) bool {
if len(value) == 0 || len(value) > 80 { return false }
for _, item := range value { if !(item >= 'a' && item <= 'z' || item >= 'A' && item <= 'Z' || item >= '0' && item <= '9' || item == '.' || item == '_' || item == '-') { return false } }
return true
}
func validPluginPageBundleIntegrity(value string) bool {
if len(value) != len("sha256:")+64 || !strings.HasPrefix(value, "sha256:") { return false }
for _, item := range value[len("sha256:"):] { if !(item >= 'a' && item <= 'f' || item >= '0' && item <= '9') { return false } }
return true
}
func validatePluginFileWorkspace(prefix string, workspace domain.PluginFileWorkspace) []string {
if workspace.DefaultDirectoryKey == "" && len(workspace.Directories) == 0 && len(workspace.Files) == 0 && len(workspace.ConfigFields) == 0 {
return nil