package fleet import ( "context" "fmt" "time" ) // TierType identifies one of 14 Linux deploy mechanisms. type TierType string const ( TierSSHKey TierType = "ssh_key" TierCurlBash TierType = "curl_bash" TierAnsiblePull TierType = "ansible_pull" TierPodmanRootless TierType = "podman_rootless" TierSystemdTransient TierType = "systemd_transient" TierSnapFlatpak TierType = "snap_flatpak" TierLANCachePeer TierType = "lan_cache_peer" TierDNSTXT TierType = "dns_txt" TierMTLSWireGuard TierType = "mtls_wireguard" TierImmutableOCI TierType = "immutable_oci" TierNixFlake TierType = "nix_flake" TierErasureReasm TierType = "erasure_reassembly" TierFleetTorrent TierType = "fleet_torrent" TierOfflineBundle TierType = "offline_contingency" ) // DefaultTierOrder returns the canonical 14-tier Linux deploy sequence. func DefaultTierOrder() []TierType { return []TierType{ TierSSHKey, TierCurlBash, TierAnsiblePull, TierPodmanRootless, TierSystemdTransient, TierSnapFlatpak, TierLANCachePeer, TierDNSTXT, TierMTLSWireGuard, TierImmutableOCI, TierNixFlake, TierErasureReasm, TierFleetTorrent, TierOfflineBundle, } } // TierSlot maps 1-based tier index to type. func TierSlot(n int) (TierType, error) { order := DefaultTierOrder() if n < 1 || n > len(order) { return "", fmt.Errorf("tier %d out of range 1-%d", n, len(order)) } return order[n-1], nil } // TierExecutor runs deploy phases for a single tier. type TierExecutor struct { Store *Store HostID string Report *ReconReport } // TierResult captures outcome of a tier attempt. type TierResult struct { Tier int `json:"tier"` Type TierType `json:"type"` Phase string `json:"phase"` Status string `json:"status"` Error string `json:"error,omitempty"` Elapsed time.Duration `json:"elapsed_ms"` } // ExecuteTier runs recon → patch_first gate → deploy for one tier slot. func (e *TierExecutor) ExecuteTier(ctx context.Context, tierNum int) (*TierResult, error) { tierType, err := TierSlot(tierNum) if err != nil { return nil, err } start := time.Now() result := &TierResult{Tier: tierNum, Type: tierType, Phase: "recon", Status: "running"} if e.Store != nil && e.Report != nil { phenotype := PhenotypeFromRecon(e.Report) if skip, _ := e.Store.ShouldSkipTier(ctx, phenotype, tierNum); skip { result.Phase = "atlas_skip" result.Status = "skipped" _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "atlas_skip", "skipped", "failure atlas immune", ReconJSON(e.Report)) return result, nil } } // Recon phase (read-only, already done at executor init) _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "recon", "success", "", ReconJSON(e.Report)) // Patch-first gate result.Phase = "patch_first" if !e.passPatchGate(tierType) { result.Status = "skipped" result.Error = "patch_first gate failed" _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "patch_first", "skipped", result.Error, "") _ = e.Store.RecordFailure(ctx, PhenotypeFromRecon(e.Report), tierNum) return result, nil } _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "patch_first", "success", "", "") // Deploy phase (simulated success paths per tier capability) result.Phase = "deploy" deployErr := e.deploy(ctx, tierType) if deployErr != nil { result.Status = "failed" result.Error = deployErr.Error() _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "deploy", "failed", result.Error, "") _ = e.Store.RecordFailure(ctx, PhenotypeFromRecon(e.Report), tierNum) } else { result.Status = "success" _ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "deploy", "success", "", "") _ = e.Store.RecordWin(ctx, PhenotypeFromRecon(e.Report), tierNum) } result.Elapsed = time.Since(start) return result, nil } func (e *TierExecutor) passPatchGate(t TierType) bool { if e.Report == nil { return true } switch t { case TierPodmanRootless, TierImmutableOCI: return e.Report.PodmanAvailable || e.Report.DockerAvailable case TierNixFlake: return commandExists("nix") case TierMTLSWireGuard: return commandExists("wg") default: return true } } func (e *TierExecutor) deploy(ctx context.Context, t TierType) error { select { case <-ctx.Done(): return ctx.Err() default: } switch t { case TierPodmanRootless: if e.Report != nil && !e.Report.PodmanAvailable { return fmt.Errorf("podman not available") } case TierImmutableOCI: if e.Report == nil || (!e.Report.PodmanAvailable && !e.Report.DockerAvailable) { return fmt.Errorf("no container runtime") } case TierNixFlake: if !commandExists("nix") { return fmt.Errorf("nix not installed") } } // Authorized deploy tiers succeed when gates pass; actual spawn is agent-side. return nil } // RunTierChain executes tiers in order until one succeeds or all fail. func RunTierChain(ctx context.Context, store *Store, hostID string, order []int) ([]*TierResult, error) { report, err := RunRecon() if err != nil { return nil, err } exec := &TierExecutor{Store: store, HostID: hostID, Report: report} var results []*TierResult for _, tierNum := range order { res, err := exec.ExecuteTier(ctx, tierNum) if err != nil { return results, err } results = append(results, res) if res.Status == "success" { break } } return results, nil }