// Package rolloutwords holds every member-facing phrase about a rollout: // the causes of an attempt (zero-downtime spec §6.3), the reasons a build // failed, the short states `carlos instances list` and the console show // (§9.1, §9.2), and the events a box writes to the platform log stream // (§9.4). One home, so the CLI, the console and the box never word one fact // two ways. Every string here is approved copy, pinned by the zero-downtime // copy record (docs/superpowers/plans/2026-10-07-zero-downtime-deploys-6-copy.json). // // A leaf: it imports nothing of this repo's, so the rollout manager and the // activator can use it without an import cycle. package rolloutwords import "fmt" // causes are the attempt causes of spec §6.3, as a member reads them. var causes = map[string]string{ "release": "new release", "install": "installed by hand", "retry": "retry", "restart": "carlos restart", "config": "config change", } // Cause renders an attempt's cause. One this binary does not know is shown // as its own word, never dropped. func Cause(c string) string { if p, ok := causes[c]; ok { return p } return c } // reasons are the box's failure words, read after "because". var reasons = map[string]string{ "health": "it did not answer its health check within 30 seconds", "settle": "it stopped within 10 seconds of answering its health check", "exit": "it exited before it was ready", "staging": "the copy on the box did not match the release", "reused": "this version name was shipped twice with different content, so ship it under a new name", } const reasonUnknown = "it failed (%s)" // Reason renders a failure word. One this binary does not know still says // the build failed, and carries the box's own word for it. func Reason(r string) string { if p, ok := reasons[r]; ok { return p } return fmt.Sprintf(reasonUnknown, r) } // Event formats (spec §9.4), one per text function in internal/rollout/events.go // plus the bounce. TestVerbsAreWhereTheCallersPutThem pins each verb count. const ( // Emitted for every attempt, overlap, hold and a socketless worker's // stop-then-start alike (rollout's execute and executeWorker), so it // says nothing about the mode (Astra plan 6 R1 #8). EventSwapping = "starting build %s (%s)" EventSwapped = "now serving build %s. The previous build finished its requests and stopped in %s" EventSwapFailed = "build %s did not take over, because %s. Still serving build %s" EventHoldDown = "build %s failed and nothing is serving. This app swaps in hold mode, so nothing falls back by itself. Roll back with carlos rollback" EventCohortHalted = "rollout of build %s stopped on box %s, because %s. Instances on that box that have not served it stay on their last good build" EventFallbackServed = "build %s failed to start, so the last good build, %s, is serving instead" EventConfigPending = "new config reached this box but is not in use yet, because %s. Trying again in %s" EventStagingRefused = "build %s is not staged correctly on this box, so the box asked for it to be staged again" EventBounceInstance = "instance restarting (%s)" EventBounceSidecar = "sidecar restarting (%s)" ) // States: the REASON column of `carlos instances list` (spec §9.1). const ( StateHeld = "held: build %s failed on this box" StateRollingOut = "rolling out" StateAsleep = "starts %s on its next request" StateConfigPending = "config delivered, not active" ) const unavailableDeclined = "its box cannot run two builds side by side, so it restarts instances the old way" // unavailablePhrases are the member-facing reasons a box cannot run zero-downtime deploys, // keyed by the codes in status.RolloutReport.Unavailable. var unavailablePhrases = map[string]string{ "no-cgroup-delegation": unavailableDeclined, "no-generation-control": unavailableDeclined, "generations-off": unavailableDeclined, "host-claims-on": unavailableDeclined, "cgroup-placement-failed": unavailableDeclined, "no-deploy-store": "its box cannot start instances: the deploy store is unavailable", "no-release-key": "its box cannot start instances: the release key is unavailable", "failure-book-unreadable": "its box cannot start instances: its record of failed builds cannot be read", } const unavailableUnknown = "its box reports a rollout problem (%s)" // Unavailable is the member-facing phrase for a box's // RolloutReport.Unavailable code. An unknown code (a newer box) reads // through unavailableUnknown with the code itself. func Unavailable(code string) string { if p, ok := unavailablePhrases[code]; ok { return p } return fmt.Sprintf(unavailableUnknown, code) } // UnavailableDeclines reports whether code is one under which the box // declined CARLOS_SWAP=overlap and runs today's stop-then-start paths. An // unknown code (a newer box) is not: a reader must not assume its instances // are safe to wake. func UnavailableDeclines(code string) bool { switch code { case "no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed": return true default: return false } } // UnavailableRefuses reports whether code is one under which the box // refuses every managed start (as opposed to running today's paths). func UnavailableRefuses(code string) bool { switch code { case "no-deploy-store", "no-release-key", "failure-book-unreadable": return true default: return false } }