carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Package rolloutwords holds every member-facing phrase about a rollout:
2// the causes of an attempt (zero-downtime spec §6.3), the reasons a build
3// failed, the short states `carlos instances list` and the console show
4// (§9.1, §9.2), and the events a box writes to the platform log stream
5// (§9.4). One home, so the CLI, the console and the box never word one fact
6// two ways. Every string here is approved copy, pinned by the zero-downtime
7// copy record (docs/superpowers/plans/2026-10-07-zero-downtime-deploys-6-copy.json).
8//
9// A leaf: it imports nothing of this repo's, so the rollout manager and the
10// activator can use it without an import cycle.
11package rolloutwords
12
13import "fmt"
14
15// causes are the attempt causes of spec §6.3, as a member reads them.
16var causes = map[string]string{
17 "release": "new release",
18 "install": "installed by hand",
19 "retry": "retry",
20 "restart": "carlos restart",
21 "config": "config change",
22}
23
24// Cause renders an attempt's cause. One this binary does not know is shown
25// as its own word, never dropped.
26func Cause(c string) string {
27 if p, ok := causes[c]; ok {
28 return p
29 }
30 return c
31}
32
33// reasons are the box's failure words, read after "because".
34var reasons = map[string]string{
35 "health": "it did not answer its health check within 30 seconds",
36 "settle": "it stopped within 10 seconds of answering its health check",
37 "exit": "it exited before it was ready",
38 "staging": "the copy on the box did not match the release",
39 "reused": "this version name was shipped twice with different content, so ship it under a new name",
40}
41
42const reasonUnknown = "it failed (%s)"
43
44// Reason renders a failure word. One this binary does not know still says
45// the build failed, and carries the box's own word for it.
46func Reason(r string) string {
47 if p, ok := reasons[r]; ok {
48 return p
49 }
50 return fmt.Sprintf(reasonUnknown, r)
51}
52
53// Event formats (spec §9.4), one per text function in internal/rollout/events.go
54// plus the bounce. TestVerbsAreWhereTheCallersPutThem pins each verb count.
55const (
56 // Emitted for every attempt, overlap, hold and a socketless worker's
57 // stop-then-start alike (rollout's execute and executeWorker), so it
58 // says nothing about the mode (Astra plan 6 R1 #8).
59 EventSwapping = "starting build %s (%s)"
60 EventSwapped = "now serving build %s. The previous build finished its requests and stopped in %s"
61 EventSwapFailed = "build %s did not take over, because %s. Still serving build %s"
62 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"
63 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"
64 EventFallbackServed = "build %s failed to start, so the last good build, %s, is serving instead"
65 EventConfigPending = "new config reached this box but is not in use yet, because %s. Trying again in %s"
66 EventStagingRefused = "build %s is not staged correctly on this box, so the box asked for it to be staged again"
67 EventBounceInstance = "instance restarting (%s)"
68 EventBounceSidecar = "sidecar restarting (%s)"
69)
70
71// States: the REASON column of `carlos instances list` (spec §9.1).
72const (
73 StateHeld = "held: build %s failed on this box"
74 StateRollingOut = "rolling out"
75 StateAsleep = "starts %s on its next request"
76 StateConfigPending = "config delivered, not active"
77)
78
79const unavailableDeclined = "its box cannot run two builds side by side, so it restarts instances the old way"
80
81// unavailablePhrases are the member-facing reasons a box cannot run zero-downtime deploys,
82// keyed by the codes in status.RolloutReport.Unavailable.
83var unavailablePhrases = map[string]string{
84 "no-cgroup-delegation": unavailableDeclined,
85 "no-generation-control": unavailableDeclined,
86 "generations-off": unavailableDeclined,
87 "host-claims-on": unavailableDeclined,
88 "cgroup-placement-failed": unavailableDeclined,
89 "no-deploy-store": "its box cannot start instances: the deploy store is unavailable",
90 "no-release-key": "its box cannot start instances: the release key is unavailable",
91 "failure-book-unreadable": "its box cannot start instances: its record of failed builds cannot be read",
92}
93
94const unavailableUnknown = "its box reports a rollout problem (%s)"
95
96// Unavailable is the member-facing phrase for a box's
97// RolloutReport.Unavailable code. An unknown code (a newer box) reads
98// through unavailableUnknown with the code itself.
99func Unavailable(code string) string {
100 if p, ok := unavailablePhrases[code]; ok {
101 return p
102 }
103 return fmt.Sprintf(unavailableUnknown, code)
104}
105
106// UnavailableDeclines reports whether code is one under which the box
107// declined CARLOS_SWAP=overlap and runs today's stop-then-start paths. An
108// unknown code (a newer box) is not: a reader must not assume its instances
109// are safe to wake.
110func UnavailableDeclines(code string) bool {
111 switch code {
112 case "no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed":
113 return true
114 default:
115 return false
116 }
117}
118
119// UnavailableRefuses reports whether code is one under which the box
120// refuses every managed start (as opposed to running today's paths).
121func UnavailableRefuses(code string) bool {
122 switch code {
123 case "no-deploy-store", "no-release-key", "failure-book-unreadable":
124 return true
125 default:
126 return false
127 }
128}
129