package runtime import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "io" "log" "os" "runtime" "strings" "time" "browser.local/run/config" "browser.local/run/protocol" ) const autonomousLifecyclePlanKey = ".platform/autonomous-lifecycle-plan.json" func LoadAutonomousLifecyclePlan(cfg config.Config) (*protocol.RunAutonomousLifecyclePlan, string, bool, error) { if strings.TrimSpace(cfg.ComponentKind) != config.PackageComponentRun || strings.TrimSpace(cfg.ServerInstanceID) == "" { log.Printf("RUN phase=autonomous_lifecycle status=skipped reason=not_generated_run server=%s component=%s", safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKind)) return nil, "", false, nil } scope, err := seededWorkspaceScope(cfg) if err != nil { log.Printf("RUN phase=autonomous_lifecycle status=scope_failed server=%s componentKey=%s error=%s", safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKey), err.Error()) return nil, "", false, err } path, err := NewWorkspaceResolver(cfg.WorkspaceRoot).ExistingTarget(scope, autonomousLifecyclePlanKey) if err != nil { if errors.Is(err, os.ErrNotExist) { log.Printf("RUN phase=autonomous_lifecycle status=skipped reason=plan_missing scope=%s", safeOptional(scope)) return nil, scope, false, nil } log.Printf("RUN phase=autonomous_lifecycle status=load_failed scope=%s error=%s", safeOptional(scope), err.Error()) return nil, scope, false, err } file, err := os.Open(path) if err != nil { log.Printf("RUN phase=autonomous_lifecycle status=open_failed scope=%s error=%s", safeOptional(scope), err.Error()) return nil, scope, false, err } defer file.Close() var plan protocol.RunAutonomousLifecyclePlan decoder := json.NewDecoder(io.LimitReader(file, 64*1024)) decoder.DisallowUnknownFields() if err := decoder.Decode(&plan); err != nil { log.Printf("RUN phase=autonomous_lifecycle status=decode_failed scope=%s error=%s", safeOptional(scope), err.Error()) return nil, scope, false, err } if err := protocol.ValidateRunAutonomousLifecyclePlan(plan); err != nil { log.Printf("RUN phase=autonomous_lifecycle status=invalid scope=%s error=%s", safeOptional(scope), err.Error()) return nil, scope, false, err } log.Printf("RUN phase=autonomous_lifecycle status=loaded server=%s plugin=%s endpoint=%s profile=%s bootstrap=%t actions=%d dependencies=%d installs=%d", plan.ServerInstanceID, plan.PluginID, plan.RunEndpointID, safeOptional(plan.ProfileKey), plan.Bootstrap != nil, len(plan.Actions), len(plan.DependencyProbes), len(plan.InstallPlans)) return &plan, scope, true, nil } func (worker *Worker) RunAutonomousLifecycleOnce(ctx context.Context) error { plan, _, ok, err := LoadAutonomousLifecyclePlan(worker.cfg) if err != nil || !ok { return err } state, err := worker.registeredState() if err != nil { return err } if err := validateAutonomousLifecycleScope(worker.cfg, state, *plan); err != nil { log.Printf("RUN phase=autonomous_lifecycle status=scope_mismatch error=%s", err.Error()) return err } if err := worker.runAutonomousDependencies(ctx, *plan); err != nil { return err } if plan.Bootstrap == nil { log.Printf("RUN phase=autonomous_lifecycle status=skipped reason=no_bootstrap server=%s", plan.ServerInstanceID) return nil } assignment := autonomousLifecycleAssignment(state, worker.cfg, *plan, *plan.Bootstrap) log.Printf("RUN phase=autonomous_lifecycle.bootstrap status=starting job=%s capability=%s target=%s operation=%s", assignment.JobID, assignment.Capability, safeOptional(assignment.TargetKey), safeOptional(assignment.ExecutionInput.LifecycleOperation)) executor := worker.executor executor.artifactHook = StaticLifecycleArtifactHook{} execution := executor.ExecuteContext(ctx, assignment) log.Printf("RUN phase=autonomous_lifecycle.bootstrap status=complete job=%s state=%s processState=%s errorCode=%s message=%s", assignment.JobID, execution.State, safeOptional(execution.ExecutionResult.ProcessState), safeOptional(execution.ErrorCode), safeOptional(execution.Message)) if err := worker.reportAutonomousLifecycle(ctx, assignment, execution); err != nil { log.Printf("RUN phase=autonomous_lifecycle.report status=degraded server=%s action=continue_worker reason=projection_report_failed", assignment.ServerInstanceID) } if execution.State != lifecycleResultStateSucceeded { return fmt.Errorf("autonomous lifecycle bootstrap failed: %s", errorSummaryFromResult(execution)) } return nil } func (worker *Worker) reportAutonomousLifecycle(ctx context.Context, assignment protocol.RunJobAssignment, execution LifecycleExecutionResult) error { state, err := worker.registeredState() if err != nil { return err } request := protocol.RunLifecycleReportRequest{ RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, ServerInstanceID: assignment.ServerInstanceID, Capability: assignment.Capability, State: execution.State, Progress: execution.Progress, Message: execution.Message, ErrorCode: execution.ErrorCode, ExecutionResult: execution.ExecutionResult, } if source, ok := worker.executor.managed.(ManagedProcessObservationSource); ok { for _, identity := range source.ManagedProcessObservations() { if identity.ServerInstanceID == assignment.ServerInstanceID && identity.RunEndpointID == state.RunEndpointID && identity.ObservationSeq > 0 && identity.State == execution.ExecutionResult.ProcessState { request.ManagedProcessID = managedProcessObservationID(identity) request.ObservationSeq = identity.ObservationSeq request.ObservedAt = identity.UpdatedAt break } } } log.Printf("RUN phase=autonomous_lifecycle.report status=starting server=%s capability=%s state=%s processState=%s", assignment.ServerInstanceID, assignment.Capability, execution.State, safeOptional(execution.ExecutionResult.ProcessState)) response, err := worker.client.ReportLifecycle(ctx, request) if err != nil { log.Printf("RUN phase=autonomous_lifecycle.report status=failed server=%s error=%s", assignment.ServerInstanceID, err.Error()) return err } if !response.Accepted || response.RunEndpointID != state.RunEndpointID || response.ServerInstanceID != assignment.ServerInstanceID { log.Printf("RUN phase=autonomous_lifecycle.report status=rejected server=%s accepted=%t", assignment.ServerInstanceID, response.Accepted) return fmt.Errorf("autonomous lifecycle report was not accepted") } log.Printf("RUN phase=autonomous_lifecycle.report status=accepted server=%s projectedState=%s", assignment.ServerInstanceID, safeOptional(response.ProjectedState)) return nil } func (worker *Worker) reportAutonomousProcessObservations(ctx context.Context) error { source, ok := worker.executor.managed.(ManagedProcessObservationSource) if !ok { return nil } state, err := worker.registeredState() if err != nil { return err } for _, identity := range source.ManagedProcessObservations() { if identity.ServerInstanceID == "" || identity.RunEndpointID != state.RunEndpointID || identity.ObservationSeq == 0 { continue } processID := managedProcessObservationID(identity) worker.observationMu.Lock() alreadyReported := worker.reportedObservations[processID] >= identity.ObservationSeq worker.observationMu.Unlock() if alreadyReported { continue } request := protocol.RunLifecycleReportRequest{RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, ServerInstanceID: identity.ServerInstanceID, Capability: protocol.RunCapabilityProcessStatus, State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "supervised process observation"}, ManagedProcessID: processID, ObservationSeq: identity.ObservationSeq, ObservedAt: identity.UpdatedAt, ExecutionResult: protocol.RunJobExecutionResult{Kind: "process", ProcessState: identity.State, ExitClassification: identity.ExitClassification, ExitCode: identity.ExitCode, Summary: "supervised process observation"}} if _, err := worker.client.ReportLifecycle(ctx, request); err != nil { return err } worker.observationMu.Lock() worker.reportedObservations[processID] = identity.ObservationSeq worker.observationMu.Unlock() } return nil } func managedProcessObservationID(identity ProcessIdentity) string { if identity.LogSessionID != "" { return "log-session:" + identity.LogSessionID } sum := sha256.Sum256([]byte(identity.RunEndpointID + "\x00" + identity.ServerInstanceID + "\x00" + identity.CommandFingerprint + "\x00" + identity.StartedAt.UTC().Format(time.RFC3339Nano))) return "sha256:" + hex.EncodeToString(sum[:]) } func validateAutonomousLifecycleScope(cfg config.Config, state WorkerState, plan protocol.RunAutonomousLifecyclePlan) error { if plan.RunEndpointID != state.RunEndpointID || plan.ServerInstanceID != cfg.ServerInstanceID || plan.PluginID != cfg.PluginID { return fmt.Errorf("autonomous lifecycle plan identity does not match this Run") } if plan.TargetOS != runtime.GOOS || plan.TargetArch != runtime.GOARCH { return fmt.Errorf("autonomous lifecycle target does not match this Run") } if cfg.ComponentKey != "" && plan.ProfileKey != "" && plan.ProfileKey != cfg.ComponentKey { return fmt.Errorf("autonomous lifecycle profile does not match this Run") } return nil } func (worker *Worker) runAutonomousDependencies(ctx context.Context, plan protocol.RunAutonomousLifecyclePlan) error { if len(plan.DependencyProbes) == 0 { log.Printf("RUN phase=autonomous_lifecycle.dependencies status=skipped reason=no_probes") return nil } for _, probe := range plan.DependencyProbes { installRoot := "" if plan.Deployment != nil { installRoot = plan.Deployment.ServerRoot } state, evidence, err := worker.executor.runDependencyProbe(ctx, probe, plan.RuntimeBindings, installRoot) if err != nil { if probe.Required { log.Printf("RUN phase=autonomous_lifecycle.dependencies status=failed probe=%s error=%s", safeOptional(probe.Key), err.Error()) return err } log.Printf("RUN phase=autonomous_lifecycle.dependencies status=optional_probe_failed probe=%s error=%s", safeOptional(probe.Key), err.Error()) continue } log.Printf("RUN phase=autonomous_lifecycle.dependencies status=probed probe=%s state=%s evidence=%s required=%t", safeOptional(probe.Key), safeOptional(state), safeOptional(evidence), probe.Required) if state == "present" || !probe.Required { continue } installPlan, found := autonomousInstallPlanForProbe(plan.InstallPlans, probe) if !found { log.Printf("RUN phase=autonomous_lifecycle.dependencies status=missing_without_install_plan probe=%s action=defer_to_bootstrap", safeOptional(probe.Key)) continue } assignment := autonomousDependencyAssignment(worker.State(), worker.cfg, plan, installPlan) input := protocol.DependencyExecutionInputResponse{JobID: assignment.JobID, ServerInstanceID: plan.ServerInstanceID, RunEndpointID: plan.RunEndpointID, PluginID: plan.PluginID, PluginVersion: plan.PluginVersion, ProfileKey: autonomousProfileKey(worker.cfg, plan), TargetOS: plan.TargetOS, TargetArch: plan.TargetArch, PlanDigest: autonomousInstallPlanDigest(installPlan), Probe: probe, Plan: installPlan, Bindings: plan.RuntimeBindings} log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_start probe=%s plan=%s steps=%d", safeOptional(probe.Key), safeOptional(installPlan.Key), len(installPlan.Steps)) if err := worker.runAutonomousInstallPlan(ctx, assignment, input); err != nil { log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_failed probe=%s plan=%s error=%s", safeOptional(probe.Key), safeOptional(installPlan.Key), err.Error()) return err } log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_complete probe=%s plan=%s", safeOptional(probe.Key), safeOptional(installPlan.Key)) } return nil } func (worker *Worker) runAutonomousInstallPlan(ctx context.Context, assignment protocol.RunJobAssignment, input protocol.DependencyExecutionInputResponse) error { for index, step := range input.Plan.Steps { if err := worker.executor.runDependencyInstallStep(ctx, assignment, input, step, index); err != nil { return err } } return nil } func autonomousInstallPlanForProbe(plans []protocol.DependencyInstallPlan, probe protocol.DependencyProbe) (protocol.DependencyInstallPlan, bool) { for _, plan := range plans { for _, step := range plan.Steps { if step.TargetKey == probe.TargetKey { return plan, true } } } return protocol.DependencyInstallPlan{}, false } func autonomousLifecycleAssignment(state WorkerState, cfg config.Config, plan protocol.RunAutonomousLifecyclePlan, action protocol.RunAutonomousLifecycleAction) protocol.RunJobAssignment { now := time.Now().UTC() profileKey := autonomousProfileKey(cfg, plan) assignment := protocol.RunJobAssignment{ JobID: "autonomous-bootstrap-" + safeWorkspaceName(action.Operation), ServerInstanceID: plan.ServerInstanceID, RunEndpointID: state.RunEndpointID, Capability: action.Capability, TargetKey: action.TargetKey, IdempotencyKey: "autonomous:" + safeWorkspaceName(plan.TargetRelease) + ":" + safeWorkspaceName(action.Operation), State: "running", LeaseToken: "local-autonomous-bootstrap", Attempt: 1, MaxAttempts: 1, CreatedAt: now, UpdatedAt: now, ExecutionInput: protocol.RunJobExecutionInput{ WorkspaceScope: profileKey, PluginID: plan.PluginID, LifecycleOperation: action.Operation, Deployment: autonomousDeploymentExecution(plan.Deployment, action.Operation), }, } if action.Capability == protocol.RunCapabilityProcessStart { assignment.ExecutionInput.LogSources = append([]protocol.RuntimeLogSourcePlan(nil), plan.LogSources...) assignment.ExecutionInput.DLLExtensions = append([]protocol.RuntimeDLLExtensionPlan(nil), plan.DLLExtensions...) } return assignment } func autonomousDependencyAssignment(state WorkerState, cfg config.Config, plan protocol.RunAutonomousLifecyclePlan, installPlan protocol.DependencyInstallPlan) protocol.RunJobAssignment { now := time.Now().UTC() return protocol.RunJobAssignment{JobID: "autonomous-dependencies-" + safeWorkspaceName(installPlan.Key), ServerInstanceID: plan.ServerInstanceID, RunEndpointID: state.RunEndpointID, Capability: protocol.RunCapabilityDependenciesInstall, TargetKey: "dependencies/install/" + installPlan.Key, IdempotencyKey: "autonomous:dependencies:" + safeWorkspaceName(installPlan.Key), State: "running", LeaseToken: "local-autonomous-dependencies", Attempt: 1, MaxAttempts: 1, CreatedAt: now, UpdatedAt: now, ExecutionInput: protocol.RunJobExecutionInput{WorkspaceScope: autonomousProfileKey(cfg, plan), PluginID: plan.PluginID, LifecycleOperation: "install"}} } func autonomousDeploymentExecution(deployment *protocol.RunAutonomousDeployment, operation string) *protocol.ServerDeploymentExecution { if deployment == nil { return nil } startCommand := deployment.StartCommand if operation == "install" && deployment.InstallCommand != "" { startCommand = deployment.InstallCommand } return &protocol.ServerDeploymentExecution{SchemaVersion: deployment.SchemaVersion, Mode: deployment.Mode, ProfileKey: deployment.ProfileKey, CreateInputs: copyStringMap(deployment.CreateInputs), ServerRoot: deployment.ServerRoot, WorkingDirectory: deployment.WorkingDirectory, StartCommand: startCommand, StopCommand: deployment.StopCommand, StatusCommand: deployment.StatusCommand, Shell: deployment.Shell, Revision: deployment.Revision} } func autonomousProfileKey(cfg config.Config, plan protocol.RunAutonomousLifecyclePlan) string { if strings.TrimSpace(cfg.ComponentKey) != "" { return cfg.ComponentKey } return plan.ProfileKey } func autonomousInstallPlanDigest(plan protocol.DependencyInstallPlan) string { body, err := json.Marshal(plan) if err != nil { return "sha256:" + strings.Repeat("0", 64) } sum := sha256.Sum256(body) return "sha256:" + hex.EncodeToString(sum[:]) } func errorSummaryFromResult(result LifecycleExecutionResult) string { if result.ErrorCode != "" && result.Message != "" { return result.ErrorCode + ": " + result.Message } if result.ErrorCode != "" { return result.ErrorCode } if result.Message != "" { return result.Message } return result.State }