Make Run runtime state authoritative

This commit is contained in:
npc0-hue
2026-08-06 10:07:01 +08:00
parent d8d07914da
commit 0875a1b17e
10 changed files with 324 additions and 12 deletions
+70
View File
@@ -113,6 +113,9 @@ func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.R
if err := svc.queueManagedGuidedDeploymentAfterRegistration(hello); err != nil {
return domain.RunControlHelloResult{}, err
}
if err := svc.queueRuntimeStateReconciliationAfterRegistration(hello, generation); err != nil {
return domain.RunControlHelloResult{}, err
}
featureFlags := []string{"control.hello", "control.heartbeat", "signed-envelope.v1.optional"}
if session.RequireSignedRequests {
featureFlags[2] = "signed-envelope.v1.required"
@@ -128,6 +131,73 @@ func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.R
}), nil
}
// queueRuntimeStateReconciliationAfterRegistration lets a generated Run correct
// stale observed state after reconnecting. Platform still owns command dispatch,
// but the Run-owned process supervisor is authoritative for whether the local
// managed process exists.
func (svc *CoreService) queueRuntimeStateReconciliationAfterRegistration(hello domain.RunControlHello, generation int) error {
if hello.ComponentKind != domain.DistributionComponentRun || strings.TrimSpace(hello.ServerInstanceID) == "" {
return nil
}
instance, err := svc.store.ServerInstances().Get(hello.ServerInstanceID)
if err != nil {
return err
}
if instance.RunEndpointID != hello.RunEndpointID || instance.RunEndpointID != dedicatedRunEndpointID(instance.ID) {
return nil
}
if instance.State != domain.ServerInstanceStateRunning && instance.State != domain.ServerInstanceStateFailed {
return nil
}
if !containsString(hello.CapabilityReport.Capabilities, domain.LifecycleCapabilityStatus) {
return nil
}
active, err := svc.hasActiveServerLifecycleJob(instance.ID)
if err != nil || active {
return err
}
plugin, endpoint, err := svc.lifecycleDependencies(instance.PluginID, instance.RunEndpointID)
if err != nil {
return err
}
if err := validateLifecycleActionRef(plugin, domain.ServerLifecycleActionStatus); err != nil {
return nil
}
if err := validateServerInstanceLifecycleDependencies(instance, plugin, endpoint, domain.ServerLifecycleActionStatus); err != nil {
return nil
}
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityStatus); err != nil {
return nil
}
_, err = svc.dispatchLifecycleJob(instance, domain.ServerLifecycleActionStatus, fmt.Sprintf("runtime-state-reconcile:%s:g%d", instance.ID, generation))
return err
}
func (svc *CoreService) hasActiveServerLifecycleJob(serverInstanceID string) (bool, error) {
jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: serverInstanceID})
if err != nil {
return false, err
}
for _, job := range jobs {
if !isLifecycleJobCapability(job.Capability) {
continue
}
if job.State == domain.JobStateQueued || job.State == domain.JobStateAccepted || job.State == domain.JobStateRunning || job.State == domain.JobStateRetrying {
return true, nil
}
}
return false, nil
}
func isLifecycleJobCapability(capability string) bool {
switch capability {
case domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus:
return true
default:
return false
}
}
// queueManagedGuidedDeploymentAfterRegistration advances only a newly-created,
// dedicated guided server. Selecting guided-install is the owner's prior
// authorization for the plugin-declared bootstrap action; reconnects remain