import Foundation /// Naming scheme for the ephemeral runners this host registers with Gitea. /// /// Every booted VM registers under a **globally unique** name. That uniqueness /// is what makes the reconcile loop safe: when a VM dies uncleanly, Gitea keeps /// the runner row forever (rows are only swept at midnight, and never at all if /// the runner never claimed a task), so we must be able to look at a runner row /// and decide "this name is mine and no live VM of mine owns it" without any /// ambiguity. A shared or reused name would make that decision impossible. public enum RunnerNaming { /// Generates a fresh runner name. /// /// - Parameter prefix: The configured prefix, e.g. `macos-vm-`. /// - Returns: `prefix` followed by a lowercase UUID, e.g. /// `macos-vm-3f1c2f8e-...`. public static func makeRunnerName(prefix: String) -> String { prefix + UUID().uuidString.lowercased() } /// Whether a runner name reported by Gitea was minted by this host. /// /// - Parameters: /// - name: A runner name from `GET /api/v1/admin/actions/runners`. /// - prefix: The configured prefix. /// - Returns: `true` when the reconcile loop may consider deleting it. public static func hasPrefix(_ name: String, prefix: String) -> Bool { // An empty prefix would match every runner on the instance, including // other people's. The reconcile loop deletes what this returns true for, // so refuse rather than match everything. `validated()` also rejects an // empty `namePrefix`; this is the second line of defence. guard !prefix.isEmpty else { return false } return name.hasPrefix(prefix) } }