From fa2ce17ce3157e942eeca11ebb355ff07bae7b4e Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Mon, 31 Aug 2026 01:20:36 +0800 Subject: [PATCH] Fix generated run lifecycle profile scope --- .../service/distribution_build_execution.go | 18 +++++++++ platform/service/resources.go | 18 ++++++++- platform/service/server_lifecycle.go | 2 +- platform/service/server_lifecycle_test.go | 39 +++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/platform/service/distribution_build_execution.go b/platform/service/distribution_build_execution.go index 1791122..98a7457 100644 --- a/platform/service/distribution_build_execution.go +++ b/platform/service/distribution_build_execution.go @@ -254,6 +254,24 @@ func defaultRunDistributionProfileKey(plugin domain.GamePlugin, profileKey strin } return profileKey } + return firstRuntimeLifecycleProfileKey(plugin) +} + +func lifecycleDefaultProfileKey(instance domain.ServerInstance, plugin domain.GamePlugin, profileKey string) string { + profileKey = strings.TrimSpace(profileKey) + if profileKey != "" { + if profile, ok := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, profileKey); ok { + return profile.Key + } + return profileKey + } + if instance.RunEndpointID != "" && instance.RunEndpointID == generatedRunEndpointID(instance.ID) { + return firstRuntimeLifecycleProfileKey(plugin) + } + return "" +} + +func firstRuntimeLifecycleProfileKey(plugin domain.GamePlugin) string { if len(plugin.RuntimeProfiles.LifecycleProfiles) == 0 { return "" } diff --git a/platform/service/resources.go b/platform/service/resources.go index 9dda60c..2a43e85 100644 --- a/platform/service/resources.go +++ b/platform/service/resources.go @@ -2260,10 +2260,24 @@ func declaredPluginFileRequest(workspace domain.PluginFileWorkspace, request dom func (svc *CoreService) runtimeProfileScope(serverInstanceID string) string { binding, err := svc.runtimeBindingForServer(serverInstanceID) - if err != nil { + if err == nil && strings.TrimSpace(binding.ProfileKey) != "" { + return binding.ProfileKey + } + if err != nil && !errors.Is(err, repo.ErrNotFound) { return "default" } - return binding.ProfileKey + instance, instanceErr := svc.store.ServerInstances().Get(serverInstanceID) + if instanceErr != nil { + return "default" + } + plugin, pluginErr := svc.store.GamePlugins().Get(instance.PluginID) + if pluginErr != nil { + return "default" + } + if profileKey := lifecycleDefaultProfileKey(instance, plugin, instance.Deployment.ProfileKey); profileKey != "" { + return profileKey + } + return "default" } func (svc *CoreService) latestMetricsForServer(instance domain.ServerInstance) domain.ServerMetrics { diff --git a/platform/service/server_lifecycle.go b/platform/service/server_lifecycle.go index 0d2f7af..5c3a9c6 100644 --- a/platform/service/server_lifecycle.go +++ b/platform/service/server_lifecycle.go @@ -319,7 +319,7 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act if err == nil && strings.TrimSpace(binding.ProfileKey) != "" { profileKey = binding.ProfileKey } else { - profileKey = instance.Deployment.ProfileKey + profileKey = lifecycleDefaultProfileKey(instance, plugin, instance.Deployment.ProfileKey) } actionRef := "" profile, hasProfile := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, profileKey) diff --git a/platform/service/server_lifecycle_test.go b/platform/service/server_lifecycle_test.go index 7a8d6a6..d0f9dea 100644 --- a/platform/service/server_lifecycle_test.go +++ b/platform/service/server_lifecycle_test.go @@ -1,11 +1,13 @@ package service import ( + "errors" "strings" "testing" "time" "browser.local/platform/domain" + "browser.local/platform/repo" ) func TestCoreServiceServerLifecycleWorkflows(t *testing.T) { @@ -104,6 +106,43 @@ func TestLifecycleProjectedStateUsesRunProcessFacts(t *testing.T) { } } +func TestGeneratedRunLifecycleUsesPackageDefaultProfileWithoutRuntimeBinding(t *testing.T) { + svc := newTestCoreService() + plugin := createLifecyclePlugin(t, svc) + plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.LifecycleCapabilityStatus) + plugin.LifecycleActions.Status = "actions/status.json" + plugin.RuntimeProfiles.LifecycleProfiles = []domain.RuntimeLifecycleProfile{{ + Key: "run-local", + Mode: "local-process", + Capabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus}, + ActionRefs: domain.PluginLifecycleActions{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Status: "actions/status.json"}, + }} + if err := svc.store.GamePlugins().Update(plugin); err != nil { + t.Fatalf("update plugin profile: %v", err) + } + ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "generated-run-owner", DisplayName: "Generated Run Owner", Email: "generated-run-owner@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"}) + serverID := "generated-run-server" + endpointID := generatedRunEndpointID(serverID) + if err := svc.store.RunEndpoints().Create(domain.RunEndpoint{ID: endpointID, DisplayName: "Generated Run", Version: "0.1.0", Status: domain.RunEndpointStatusOnline, Capabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus}, Capacity: domain.RunCapacity{MaxJobs: 1}, LastHeartbeatAt: fixedTime}); err != nil { + t.Fatalf("create generated run endpoint: %v", err) + } + instance := domain.ServerInstance{ID: serverID, PluginID: plugin.ID, PluginVersion: plugin.Version, RunEndpointID: endpointID, Name: "Generated Run Server", OwnerUserID: "generated-run-owner", State: domain.ServerInstanceStateFailed, ConfigVersion: 1, Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeExisting, ServerRoot: `C:\scumserver`, Revision: 1}, CreatedAt: fixedTime, UpdatedAt: fixedTime} + if err := svc.store.ServerInstances().Create(instance); err != nil { + t.Fatalf("create generated run server: %v", err) + } + if _, err := svc.runtimeBindingForServer(serverID); !errors.Is(err, repo.ErrNotFound) { + t.Fatalf("expected generated run server to have no manual runtime binding: %v", err) + } + + result, err := svc.QueryServerInstanceProcessForSession(ownerSession, domain.ServerLifecycleCommand{ServerInstanceID: serverID, ExpectedConfigVersion: 1, IdempotencyKey: "generated-run-status"}) + if err != nil { + t.Fatalf("query generated run status: %v", err) + } + if result.Job.ExecutionInput.WorkspaceScope != "run-local" || result.Job.TargetKey != "actions/status.json" { + t.Fatalf("expected generated run status to use packaged profile scope, job=%+v", result.Job) + } +} + func TestLifecycleJobResultsPublishProcessStateEvents(t *testing.T) { svc, sessionToken := newLifecycleRunService(t) createLifecyclePlugin(t, svc)