feat: 清理openspec
This commit is contained in:
@@ -30,7 +30,7 @@ func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.R
|
||||
return domain.RunControlHelloResult{}, err
|
||||
}
|
||||
if hasComponentAuthIdentity(hello) {
|
||||
if err := svc.validateDedicatedRunHello(hello); err != nil {
|
||||
if err := svc.validateAuthenticatedRunHello(hello); err != nil {
|
||||
return domain.RunControlHelloResult{}, err
|
||||
}
|
||||
auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
@@ -76,6 +76,19 @@ func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.R
|
||||
if err := svc.upsertRunEndpoint(endpoint); err != nil {
|
||||
return domain.RunControlHelloResult{}, err
|
||||
}
|
||||
if hello.ServerInstanceID != "" && hello.ComponentKind == domain.DistributionComponentRun {
|
||||
instance, instanceErr := svc.store.ServerInstances().Get(hello.ServerInstanceID)
|
||||
if instanceErr != nil {
|
||||
return domain.RunControlHelloResult{}, instanceErr
|
||||
}
|
||||
if instance.RunEndpointID != hello.RunEndpointID {
|
||||
instance.RunEndpointID = hello.RunEndpointID
|
||||
instance.UpdatedAt = stamp
|
||||
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
||||
return domain.RunControlHelloResult{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
previous, previousErr := svc.store.RunControlSessions().Get(hello.RunEndpointID)
|
||||
generation := 1
|
||||
if previousErr == nil {
|
||||
@@ -126,19 +139,22 @@ func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.R
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (svc *CoreService) validateDedicatedRunHello(hello domain.RunControlHello) error {
|
||||
func (svc *CoreService) validateAuthenticatedRunHello(hello domain.RunControlHello) error {
|
||||
if hello.ComponentKind != domain.DistributionComponentRun {
|
||||
return validationError("component-authenticated run hello must use the run component")
|
||||
}
|
||||
if hello.RunEndpointID == platformDistributionBuilderEndpointID {
|
||||
return validationError("platform distribution builder cannot register as a server Run")
|
||||
}
|
||||
instance, err := svc.store.ServerInstances().Get(hello.ServerInstanceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(instance.DeploymentTargetID) == "" && instance.RunEndpointID != dedicatedRunEndpointID(instance.ID) {
|
||||
return nil // legacy Run registrations keep their historical endpoint contract.
|
||||
if hello.PluginID != instance.PluginID {
|
||||
return validationError("Run plugin identity does not match the server instance")
|
||||
}
|
||||
if hello.PluginID != instance.PluginID || hello.RunEndpointID != instance.RunEndpointID {
|
||||
return validationError("run endpoint identity does not match the server binding")
|
||||
if instance.State == domain.ServerInstanceStateDeleted {
|
||||
return validationError("deleted server cannot attach an active Run heartbeat")
|
||||
}
|
||||
instances, err := svc.store.ServerInstances().List(domain.ServerInstanceFilter{RunEndpointID: hello.RunEndpointID})
|
||||
if err != nil {
|
||||
|
||||
@@ -205,7 +205,6 @@ func TestCoreServiceRunHelloRejectsStalePackageKeyAfterReset(t *testing.T) {
|
||||
|
||||
func TestCoreServiceRunHelloRejectsGeneratedRunOnPromotedBuildEndpoint(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
builderID := instance.RunEndpointID
|
||||
instance.State = domain.ServerInstanceStateFailed
|
||||
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
||||
t.Fatalf("mark legacy server failed: %v", err)
|
||||
@@ -213,28 +212,28 @@ func TestCoreServiceRunHelloRejectsGeneratedRunOnPromotedBuildEndpoint(t *testin
|
||||
if _, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "promoted-hello-fence"}); err != nil {
|
||||
t.Fatalf("generate promoted Run: %v", err)
|
||||
}
|
||||
migrated, err := svc.GetServerInstance(instance.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get migrated server: %v", err)
|
||||
}
|
||||
key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "")
|
||||
if err != nil {
|
||||
t.Fatalf("get component key: %v", err)
|
||||
}
|
||||
hello := validRunControlHello()
|
||||
hello.RunEndpointID = builderID
|
||||
hello.RunEndpointID = platformDistributionBuilderEndpointID
|
||||
hello.RegistrationToken = plainKey
|
||||
hello.ServerInstanceID = instance.ID
|
||||
hello.PluginID = instance.PluginID
|
||||
hello.ComponentKind = domain.DistributionComponentRun
|
||||
hello.KeyGeneration = key.Generation
|
||||
if _, err := svc.RegisterRunHello(hello); err == nil || !strings.Contains(err.Error(), "does not match") {
|
||||
if _, err := svc.RegisterRunHello(hello); err == nil || !strings.Contains(err.Error(), "platform distribution builder") {
|
||||
t.Fatalf("expected shared builder registration rejection, got %v", err)
|
||||
}
|
||||
|
||||
hello.RunEndpointID = migrated.RunEndpointID
|
||||
hello.RunEndpointID = generatedRunEndpointID(instance.ID)
|
||||
if result, err := svc.RegisterRunHello(hello); err != nil || !result.Accepted {
|
||||
t.Fatalf("expected dedicated Run registration acceptance, result=%+v err=%v", result, err)
|
||||
t.Fatalf("expected automatically discovered Run registration acceptance, result=%+v err=%v", result, err)
|
||||
}
|
||||
attached, err := svc.GetServerInstance(instance.ID)
|
||||
if err != nil || attached.RunEndpointID != hello.RunEndpointID {
|
||||
t.Fatalf("expected heartbeat to attach active Run endpoint, instance=%+v err=%v", attached, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -274,19 +274,11 @@ func TestCoreServiceGeneratedRunOnlyEndpointCanGenerateAnotherRun(t *testing.T)
|
||||
packageConfig := readGeneratedPackageConfig(t, svc, session, first.ArtifactID)
|
||||
instance, err = svc.GetServerInstance(instance.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get dedicated Run binding: %v", err)
|
||||
}
|
||||
bootstrap, err := svc.store.RunEndpoints().Get(instance.DeploymentTargetID)
|
||||
if err != nil {
|
||||
t.Fatalf("get former bootstrap endpoint: %v", err)
|
||||
}
|
||||
bootstrap.Status = domain.RunEndpointStatusOffline
|
||||
if err := svc.store.RunEndpoints().Update(bootstrap); err != nil {
|
||||
t.Fatalf("take former bootstrap endpoint offline: %v", err)
|
||||
t.Fatalf("get server after Run build: %v", err)
|
||||
}
|
||||
|
||||
helloRequest := validRunControlHello()
|
||||
helloRequest.RunEndpointID = instance.RunEndpointID
|
||||
helloRequest.RunEndpointID = first.RunEndpointID
|
||||
helloRequest.RegistrationToken = packageConfig.AuthKey
|
||||
helloRequest.ServerInstanceID = instance.ID
|
||||
helloRequest.PluginID = instance.PluginID
|
||||
@@ -304,7 +296,7 @@ func TestCoreServiceGeneratedRunOnlyEndpointCanGenerateAnotherRun(t *testing.T)
|
||||
t.Fatalf("register generated Run: result=%+v err=%v", registered, err)
|
||||
}
|
||||
online, err := svc.store.RunEndpoints().List(domain.RunEndpointFilter{Status: domain.RunEndpointStatusOnline})
|
||||
if err != nil || len(online) != 1 || online[0].ID != instance.RunEndpointID {
|
||||
if err != nil || len(online) != 1 || online[0].ID != first.RunEndpointID {
|
||||
t.Fatalf("expected generated Run to be the only online endpoint: endpoints=%+v err=%v", online, err)
|
||||
}
|
||||
for _, capability := range online[0].Capabilities {
|
||||
@@ -313,7 +305,7 @@ func TestCoreServiceGeneratedRunOnlyEndpointCanGenerateAnotherRun(t *testing.T)
|
||||
}
|
||||
}
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
RunEndpointID: first.RunEndpointID,
|
||||
SessionToken: registered.SessionToken,
|
||||
Capabilities: []string{domain.JobCapabilityDistributionBuild},
|
||||
Capacity: domain.RunCapacity{MaxJobs: 1},
|
||||
|
||||
@@ -46,9 +46,6 @@ func (svc *CoreService) GenerateRunDistributionForSession(sessionID string, requ
|
||||
return domain.RunDistribution{}, err
|
||||
}
|
||||
}
|
||||
if err := svc.promoteLegacyRunBinding(&instance); err != nil {
|
||||
return domain.RunDistribution{}, err
|
||||
}
|
||||
if ready, reason := svc.distributionBuilderReadiness(); !ready {
|
||||
_ = svc.recordAuditEvent(user.ID, "run.generate.denied", "server-instance", instance.ID, domain.AuditResultDenied, reason)
|
||||
return domain.RunDistribution{}, validationError(reason)
|
||||
@@ -72,7 +69,7 @@ func (svc *CoreService) GenerateRunDistributionForSession(sessionID string, requ
|
||||
ID: distributionID,
|
||||
ServerInstanceID: instance.ID,
|
||||
PluginID: plugin.ID,
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
RunEndpointID: runEndpointIDForDistribution(instance),
|
||||
TargetOS: request.TargetOS,
|
||||
TargetArch: request.TargetArch,
|
||||
PackageFormat: runPackageFormatForTarget(request.TargetOS),
|
||||
@@ -123,29 +120,6 @@ func (svc *CoreService) GenerateRunDistributionForSession(sessionID string, requ
|
||||
return domain.CopyRunDistribution(distribution), nil
|
||||
}
|
||||
|
||||
// promoteLegacyRunBinding reserves the server-scoped endpoint used by a
|
||||
// generated Run. A legacy shared endpoint remains an optional deployment target
|
||||
// for non-build workflows; distribution builds are always platform-owned.
|
||||
func (svc *CoreService) promoteLegacyRunBinding(instance *domain.ServerInstance) error {
|
||||
if instance == nil || strings.TrimSpace(instance.DeploymentTargetID) != "" || (instance.State != domain.ServerInstanceStateDraft && instance.State != domain.ServerInstanceStateFailed) {
|
||||
return nil
|
||||
}
|
||||
currentEndpointID := strings.TrimSpace(instance.RunEndpointID)
|
||||
dedicatedEndpointID := dedicatedRunEndpointID(instance.ID)
|
||||
if currentEndpointID == dedicatedEndpointID {
|
||||
return nil
|
||||
}
|
||||
if currentEndpointID != "" {
|
||||
instance.DeploymentTargetID = currentEndpointID
|
||||
}
|
||||
instance.RunEndpointID = dedicatedEndpointID
|
||||
instance.UpdatedAt = svc.now()
|
||||
if err := validator.ValidateServerInstance(*instance); err != nil {
|
||||
return err
|
||||
}
|
||||
return svc.store.ServerInstances().Update(*instance)
|
||||
}
|
||||
|
||||
func (svc *CoreService) GenerateClientManagerDistributionForSession(sessionID string, request domain.ClientManagerBuildRequest) (domain.ClientManagerDistribution, error) {
|
||||
request = domain.CopyClientManagerBuildRequest(request)
|
||||
if strings.TrimSpace(request.IdempotencyKey) == "" {
|
||||
@@ -458,9 +432,6 @@ func (svc *CoreService) GetServerRuntimeActionsForSession(sessionID string, serv
|
||||
}
|
||||
endpoint, endpointErr := svc.store.RunEndpoints().Get(instance.RunEndpointID)
|
||||
runRegistered := endpointErr == nil
|
||||
if endpointErr != nil && strings.TrimSpace(instance.DeploymentTargetID) != "" {
|
||||
endpoint, endpointErr = svc.store.RunEndpoints().Get(instance.DeploymentTargetID)
|
||||
}
|
||||
if endpointErr != nil && !errors.Is(endpointErr, repo.ErrNotFound) {
|
||||
return domain.ServerRuntimeActions{}, endpointErr
|
||||
}
|
||||
@@ -506,15 +477,15 @@ func (svc *CoreService) GetServerRuntimeActionsForSession(sessionID string, serv
|
||||
Actions: []domain.ServerRuntimeAction{
|
||||
runtimeAction("generate-run", "Generate run", pluginDeclares(plugin, "server.run.distribution") && builderReady && runPackageInputsComplete, fallbackReason(!pluginDeclares(plugin, "server.run.distribution"), "plugin permission is not declared", fallbackReason(!builderReady, builderReason, runPackageReason))),
|
||||
runtimeAction("download-run", "Download run", hasAvailableRunPackage, "run package has not been generated"),
|
||||
runtimeAction("push-run-update", "Push run update", runRegistered && pluginDeclares(plugin, "server.run.distribution") && svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate) && runPackageInputsComplete, fallbackReason(!runRegistered, "dedicated Run has not registered", fallbackReason(!pluginDeclares(plugin, "server.run.distribution") || !svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate), "run endpoint cannot self-update", runPackageReason))),
|
||||
runtimeAction("push-run-update", "Push run update", runRegistered && pluginDeclares(plugin, "server.run.distribution") && svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate) && runPackageInputsComplete, fallbackReason(!runRegistered, "Run heartbeat has not been observed", fallbackReason(!pluginDeclares(plugin, "server.run.distribution") || !svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate), "run endpoint cannot self-update", runPackageReason))),
|
||||
runtimeAction("reset-run-key", "Reset run key", pluginDeclares(plugin, "server.run.distribution"), "plugin permission is not declared"),
|
||||
runtimeAction("generate-client-manager", "Generate client manager", pluginDeclares(plugin, "server.client-manager.manage") && builderReady && bindingsComplete, fallbackReason(!pluginDeclares(plugin, "server.client-manager.manage"), "client-manager permission is not declared", fallbackReason(!builderReady, builderReason, bindingReason))),
|
||||
runtimeAction("download-client-manager", "Download client manager", hasAvailableClientPackage, "client-manager package has not been generated"),
|
||||
runtimeAction("reset-client-manager-key", "Reset client-manager key", pluginDeclares(plugin, "server.client-manager.manage"), "client-manager permission is not declared"),
|
||||
runtimeAction("dependencies-check", "Check dependencies", runRegistered && dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck) && bindingsComplete, fallbackReason(!runRegistered, "dedicated Run has not registered", fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck), "run endpoint cannot check dependencies", bindingReason)))),
|
||||
runtimeAction("dependencies-install", "Install dependencies", runRegistered && dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall) && bindingsComplete, fallbackReason(!runRegistered, "dedicated Run has not registered", fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall), "run endpoint cannot install dependencies", bindingReason)))),
|
||||
runtimeAction("live-logs", "Live logs", runRegistered && pluginSupports(plugin, "logs.read"), fallbackReason(!runRegistered, "dedicated Run has not registered", "plugin does not declare live logs")),
|
||||
runtimeAction("historical-logs", "Historical logs", runRegistered && svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill) && bindingsComplete, fallbackReason(!runRegistered, "dedicated Run has not registered", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill), "run endpoint cannot backfill logs", bindingReason))),
|
||||
runtimeAction("dependencies-check", "Check dependencies", runRegistered && dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck) && bindingsComplete, fallbackReason(!runRegistered, "Run heartbeat has not been observed", fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck), "run endpoint cannot check dependencies", bindingReason)))),
|
||||
runtimeAction("dependencies-install", "Install dependencies", runRegistered && dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall) && bindingsComplete, fallbackReason(!runRegistered, "Run heartbeat has not been observed", fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall), "run endpoint cannot install dependencies", bindingReason)))),
|
||||
runtimeAction("live-logs", "Live logs", runRegistered && pluginSupports(plugin, "logs.read"), fallbackReason(!runRegistered, "Run heartbeat has not been observed", "plugin does not declare live logs")),
|
||||
runtimeAction("historical-logs", "Historical logs", runRegistered && svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill) && bindingsComplete, fallbackReason(!runRegistered, "Run heartbeat has not been observed", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill), "run endpoint cannot backfill logs", bindingReason))),
|
||||
},
|
||||
}
|
||||
if runRegistered {
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestCoreServiceBuildsSCUMGuidedRunWithoutCompleteRuntimeBinding(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServicePromotesLegacyRunBindingBeforeDistributionBuild(t *testing.T) {
|
||||
func TestCoreServiceDoesNotPrebindLegacyRunBeforeDistributionBuild(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
legacyEndpointID := instance.RunEndpointID
|
||||
instance.State = domain.ServerInstanceStateFailed
|
||||
@@ -165,18 +165,18 @@ func TestCoreServicePromotesLegacyRunBindingBeforeDistributionBuild(t *testing.T
|
||||
|
||||
distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "legacy-promote-build"})
|
||||
if err != nil {
|
||||
t.Fatalf("generate promoted legacy Run: %v", err)
|
||||
t.Fatalf("generate Run without prebinding: %v", err)
|
||||
}
|
||||
migrated, err := svc.GetServerInstance(instance.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get migrated server: %v", err)
|
||||
}
|
||||
if migrated.DeploymentTargetID != legacyEndpointID || migrated.RunEndpointID != "server-run-"+instance.ID {
|
||||
t.Fatalf("expected legacy binding promotion, got %+v", migrated)
|
||||
if migrated.DeploymentTargetID != "" || migrated.RunEndpointID != legacyEndpointID {
|
||||
t.Fatalf("Run generation must not change the server's active endpoint, got %+v", migrated)
|
||||
}
|
||||
job, err := svc.GetJob(distribution.BuildJobID)
|
||||
if err != nil || job.RunEndpointID != platformDistributionBuilderEndpointID || distribution.RunEndpointID != migrated.RunEndpointID {
|
||||
t.Fatalf("expected platform build and dedicated package endpoint %q, job=%+v distribution=%+v err=%v", migrated.RunEndpointID, job, distribution, err)
|
||||
if err != nil || job.RunEndpointID != platformDistributionBuilderEndpointID || distribution.RunEndpointID != legacyEndpointID {
|
||||
t.Fatalf("expected platform build and unchanged package endpoint %q, job=%+v distribution=%+v err=%v", legacyEndpointID, job, distribution, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import "browser.local/platform/domain"
|
||||
|
||||
func applyPluginCreateDefaults(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition) domain.ServerDeploymentDefinition {
|
||||
definition = domain.CopyServerDeploymentDefinition(definition)
|
||||
if definition.ProfileKey == "" && len(plugin.RuntimeProfiles.LifecycleProfiles) > 0 {
|
||||
definition.ProfileKey = plugin.RuntimeProfiles.LifecycleProfiles[0].Key
|
||||
}
|
||||
if definition.Mode != domain.ServerDeploymentModeGuided {
|
||||
return definition
|
||||
}
|
||||
|
||||
@@ -60,10 +60,7 @@ func (svc *CoreService) UpdateServerDeploymentForSession(sessionID, serverInstan
|
||||
return domain.ServerDeploymentView{}, err
|
||||
}
|
||||
if update.RunEndpointID != "" {
|
||||
if _, err := svc.store.RunEndpoints().Get(update.RunEndpointID); err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
}
|
||||
instance.RunEndpointID = update.RunEndpointID
|
||||
return domain.ServerDeploymentView{}, validationError("run endpoint identity is managed by Run heartbeat")
|
||||
}
|
||||
instance.Deployment = definition
|
||||
instance.DeploymentProjection = domain.ServerDeploymentProjection{}
|
||||
@@ -105,12 +102,9 @@ func (svc *CoreService) deployServerInstance(command domain.ServerLifecycleComma
|
||||
return domain.ServerLifecycleResult{}, validationError("deployment definition is required")
|
||||
}
|
||||
if strings.TrimSpace(instance.RunEndpointID) == "" {
|
||||
return domain.ServerLifecycleResult{}, validationError("run endpoint must be selected before deployment")
|
||||
return domain.ServerLifecycleResult{}, validationError("an active Run heartbeat is required for legacy manual deployment dispatch")
|
||||
}
|
||||
if _, err := svc.store.RunEndpoints().Get(instance.RunEndpointID); err != nil {
|
||||
if errors.Is(err, repo.ErrNotFound) && strings.TrimSpace(instance.DeploymentTargetID) != "" {
|
||||
return domain.ServerLifecycleResult{}, validationError("dedicated Run must register before deployment")
|
||||
}
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
plugin, endpoint, err := svc.lifecycleDependencies(instance.PluginID, instance.RunEndpointID)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
func TestCoreServiceSavesDraftDeploymentRedactsReadsAndDispatchesOnlyToCompatibleRun(t *testing.T) {
|
||||
func TestCoreServiceSavesDraftDeploymentRedactsReadsAndKeepsRunIdentityHeartbeatManaged(t *testing.T) {
|
||||
svc, _ := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "deployment-owner", DisplayName: "Deployment Owner", Email: "deployment-owner@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||
@@ -37,31 +37,8 @@ func TestCoreServiceSavesDraftDeploymentRedactsReadsAndDispatchesOnlyToCompatibl
|
||||
t.Fatalf("expected explicit deployment reveal, reveal=%+v err=%v", revealed, err)
|
||||
}
|
||||
|
||||
if _, err := svc.UpdateServerDeploymentForSession(ownerSession, draft.Instance.ID, domain.ServerDeploymentUpdate{RunEndpointID: "run-local", Mode: domain.ServerDeploymentModeCustom}); err != nil {
|
||||
t.Fatalf("bind draft to run: %v", err)
|
||||
}
|
||||
if _, err := svc.DeployServerInstanceForSession(ownerSession, domain.ServerLifecycleCommand{ServerInstanceID: draft.Instance.ID, ExpectedConfigVersion: draft.Instance.ConfigVersion, IdempotencyKey: "deploy-incompatible"}); err == nil || !strings.Contains(err.Error(), "deployment.plan.v1") {
|
||||
t.Fatalf("expected incompatible Run rejection, got %v", err)
|
||||
}
|
||||
|
||||
endpoint, err := svc.store.RunEndpoints().Get("run-local")
|
||||
if err != nil {
|
||||
t.Fatalf("get endpoint: %v", err)
|
||||
}
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan)
|
||||
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||
t.Fatalf("enable deployment capability: %v", err)
|
||||
}
|
||||
deployed, err := svc.DeployServerInstanceForSession(ownerSession, domain.ServerLifecycleCommand{ServerInstanceID: draft.Instance.ID, ExpectedConfigVersion: draft.Instance.ConfigVersion, IdempotencyKey: "deploy-compatible"})
|
||||
if err != nil {
|
||||
t.Fatalf("deploy compatible draft: %v", err)
|
||||
}
|
||||
if deployed.Job.ExecutionInput.Deployment == nil || deployed.Job.ExecutionInput.Deployment.StartCommand != "/srv/venv-server/.venv/bin/python server.py" || deployed.Job.Progress.Phase != "queued" {
|
||||
t.Fatalf("Run job must carry protected plan and queued phase: %+v", deployed.Job)
|
||||
}
|
||||
view, err = svc.GetServerDeploymentForSession(ownerSession, draft.Instance.ID)
|
||||
if err != nil || view.LatestDispatch == nil || view.LatestDispatch.JobID != deployed.Job.ID || view.LatestDispatch.DeploymentRevision != deployed.Job.ExecutionInput.Deployment.Revision || !view.LatestDispatch.DeploymentDefinitionIncluded {
|
||||
t.Fatalf("expected safe dispatch evidence, view=%+v err=%v", view, err)
|
||||
if _, err := svc.UpdateServerDeploymentForSession(ownerSession, draft.Instance.ID, domain.ServerDeploymentUpdate{RunEndpointID: "run-local", Mode: domain.ServerDeploymentModeCustom}); err == nil || !strings.Contains(err.Error(), "managed by Run heartbeat") {
|
||||
t.Fatalf("expected Run identity update to be rejected, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,10 +152,24 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
}), nil
|
||||
}
|
||||
|
||||
func dedicatedRunEndpointID(serverInstanceID string) string {
|
||||
func generatedRunEndpointID(serverInstanceID string) string {
|
||||
return "server-run-" + serverInstanceID
|
||||
}
|
||||
|
||||
// dedicatedRunEndpointID remains a compatibility helper for legacy fixtures.
|
||||
// Production flow uses generatedRunEndpointID only when building a package and
|
||||
// attaches the active endpoint from the first authenticated Run heartbeat.
|
||||
func dedicatedRunEndpointID(serverInstanceID string) string {
|
||||
return generatedRunEndpointID(serverInstanceID)
|
||||
}
|
||||
|
||||
func runEndpointIDForDistribution(instance domain.ServerInstance) string {
|
||||
if endpointID := strings.TrimSpace(instance.RunEndpointID); endpointID != "" {
|
||||
return endpointID
|
||||
}
|
||||
return generatedRunEndpointID(instance.ID)
|
||||
}
|
||||
|
||||
func (svc *CoreService) CreateServerInstanceWorkflowForSession(sessionID string, create domain.ServerLifecycleCreate) (domain.ServerLifecycleResult, error) {
|
||||
user, err := svc.GetCurrentUser(sessionID)
|
||||
if err != nil {
|
||||
|
||||
@@ -159,43 +159,36 @@ func assertLogProcessStateEvent(t *testing.T, subscription LogEventSubscription,
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceCreatesTargetBoundDraftAndRequiresDedicatedRunRegistration(t *testing.T) {
|
||||
func TestCoreServiceCreatesUnboundDraftAndAttachesRunFromHeartbeat(t *testing.T) {
|
||||
svc, _ := newLifecycleRunService(t)
|
||||
plugin := createLifecyclePlugin(t, svc)
|
||||
|
||||
draft, err := svc.CreateServerInstanceWorkflow(domain.ServerLifecycleCreate{
|
||||
ID: "server-dedicated", PluginID: plugin.ID, DeploymentTargetID: "run-local", Name: "Dedicated SCUM", IdempotencyKey: "dedicated-draft", ProfileKey: "local",
|
||||
})
|
||||
draft, err := svc.CreateServerInstanceWorkflow(domain.ServerLifecycleCreate{ID: "server-dedicated", PluginID: plugin.ID, Name: "Dedicated SCUM", IdempotencyKey: "dedicated-draft"})
|
||||
if err != nil {
|
||||
t.Fatalf("create target-bound draft: %v", err)
|
||||
}
|
||||
if draft.Instance.State != domain.ServerInstanceStateDraft || draft.Job.ID != "" || draft.Instance.DeploymentTargetID != "run-local" || draft.Instance.RunEndpointID != "server-run-server-dedicated" {
|
||||
t.Fatalf("expected draft with separate target and reserved Run identity, got %+v", draft)
|
||||
}
|
||||
if _, err := svc.DeployServerInstanceForSession("", domain.ServerLifecycleCommand{ServerInstanceID: draft.Instance.ID, ExpectedConfigVersion: draft.Instance.ConfigVersion, IdempotencyKey: "before-register"}); err == nil {
|
||||
t.Fatal("expected deployment without a registered dedicated Run to fail")
|
||||
if draft.Instance.State != domain.ServerInstanceStateDraft || draft.Job.ID != "" || draft.Instance.RunEndpointID != "" {
|
||||
t.Fatalf("expected draft without a reserved Run identity, got %+v", draft)
|
||||
}
|
||||
|
||||
key, plainKey, err := svc.ensureActiveComponentKey(draft.Instance.ID, domain.DistributionComponentRun, "")
|
||||
if err != nil {
|
||||
t.Fatalf("create Run key: %v", err)
|
||||
}
|
||||
wrong := validRunControlHello()
|
||||
wrong.ServerInstanceID = draft.Instance.ID
|
||||
wrong.PluginID = plugin.ID
|
||||
wrong.ComponentKind = domain.DistributionComponentRun
|
||||
wrong.KeyGeneration = key.Generation
|
||||
wrong.RegistrationToken = plainKey
|
||||
wrong.RunEndpointID = "run-local"
|
||||
if _, err := svc.RegisterRunHello(wrong); err == nil || !strings.Contains(err.Error(), "does not match") {
|
||||
t.Fatalf("expected mismatched endpoint registration rejection, got %v", err)
|
||||
hello := validRunControlHello()
|
||||
hello.ServerInstanceID = draft.Instance.ID
|
||||
hello.PluginID = plugin.ID
|
||||
hello.ComponentKind = domain.DistributionComponentRun
|
||||
hello.KeyGeneration = key.Generation
|
||||
hello.RegistrationToken = plainKey
|
||||
hello.RunEndpointID = "run-local"
|
||||
hello.DisplayName = "Automatic SCUM Run"
|
||||
if registered, err := svc.RegisterRunHello(hello); err != nil || !registered.Accepted {
|
||||
t.Fatalf("register automatic Run heartbeat: result=%+v err=%v", registered, err)
|
||||
}
|
||||
|
||||
correct := wrong
|
||||
correct.RunEndpointID = draft.Instance.RunEndpointID
|
||||
correct.DisplayName = "Dedicated SCUM Run"
|
||||
if registered, err := svc.RegisterRunHello(correct); err != nil || !registered.Accepted {
|
||||
t.Fatalf("register dedicated Run: result=%+v err=%v", registered, err)
|
||||
attached, err := svc.GetServerInstance(draft.Instance.ID)
|
||||
if err != nil || attached.RunEndpointID != "run-local" {
|
||||
t.Fatalf("expected heartbeat to attach Run endpoint, instance=%+v err=%v", attached, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user