package status import ( "context" "encoding/json" "errors" "log" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) // FleetApp is one opted-in app's public row — name, count, health, and the // release it's running. Nothing here exists for an app that hasn't set // StatusNamed; see BuildFleet's apps grouping. type FleetApp struct { Name string `json:"name"` Instances int `json:"instances"` Healthy bool `json:"healthy"` Channel string `json:"channel"` Version string `json:"version"` // short sha as stored on the route } // FleetBox is a Box's public row inside the merged feed: everything a Box // carries except its own Replication (which only ever votes into the // fleet-wide Replication, never appears per-box) and its own Capacity // (same — it only ever votes into Fleet.Capacity), plus Stale, which only // exists at merge time since a lone Box has no "how long ago" to compare // against. type FleetBox struct { Label string `json:"label"` Region string `json:"region,omitempty"` Load *string `json:"load"` Stale bool `json:"stale"` Routes RouteCounts `json:"routes"` // AdoptionRunning DOES appear per-box, unlike Replication and Capacity // above, because it is the one reading whose whole value is knowing WHICH // box has it wrong: "something in the fleet isn't adopting" sends an // operator to ssh into every box in turn, which is barely better than the // silence this field exists to end. It carries nothing about a tenant — // only whether the platform's own timer is armed on an opaquely-labelled // machine. AdoptionRunning *bool `json:"adoption_running"` // ConfigPathRunning appears per-box for exactly the reason // AdoptionRunning does: the value of the reading is knowing WHICH box // lost its config-sync trigger, since "somewhere in the fleet config is // an hour late" is a search, not an answer. Deliberately NOT rolled up // into a fleet-wide field: worst-of aggregates exist here only where the // page shows a tile for them, and this one is a per-box operational // fact, not a headline about the fleet. ConfigPathRunning *bool `json:"config_path_running"` // RestartPathRunning appears per-box for exactly the reason // ConfigPathRunning does, and is not rolled up for the same reason: it is a // per-box operational fact ("this box's restart trigger is not watching"), // not a headline about the fleet. RestartPathRunning *bool `json:"restart_path_running"` // ExpiredCanaries appears per-box for the same reason AdoptionRunning // does: the fleet-wide sum says something is expired somewhere, this // says on which box, without naming which route. Published in the FEED // only for now — the rendered boxes table deliberately has no column // for it yet (2026-08-03 review, MINOR 4: this comment used to imply // the table showed it; it does not, and adding the column is deferred // until an expired canary has actually been hunted via the feed once). ExpiredCanaries *int `json:"expired_canaries"` } // Fleet is the merged feed: status/fleet.json. Every field here is either a // fleet-wide aggregate, a band word, or behind an app's own opt-in — see the // design spec's "rule for new fields". type Fleet struct { GeneratedAt time.Time `json:"generated_at"` Boxes []FleetBox `json:"boxes"` Instances map[string]*int `json:"instances"` // total/static/resident/hibernating/awake/asleep Capacity struct { Level *string `json:"level"` } `json:"capacity"` Apps []FleetApp `json:"apps"` AppsUnlisted int `json:"apps_unlisted"` Services []struct { Name string `json:"name"` Up bool `json:"up"` } `json:"services"` Replication Replication `json:"replication"` AdoptionRunning *bool `json:"adoption_running"` // ExpiredCanaries is the fleet-wide sum of every box's own count — see // sumExpiredCanaries. Null-propagating: one box that couldn't check // nulls the whole aggregate, the same rule sumInstances applies to // Awake/Asleep, because a partial sum would read as more certain than // it is. The status page shows this only when it is a known, positive // count — see assets/index.html. ExpiredCanaries *int `json:"expired_canaries"` } // capacityRank orders the capacity band words worst-last, so "worst of a // set of boxes" is just "the box with the highest rank" — built off // snapshot.go's capacityWords so the two never drift apart into disagreeing // about which word beats which. var capacityRank = map[string]int{ capacityWords[0]: 0, capacityWords[1]: 1, capacityWords[2]: 2, } // intPtr is the one-line "give me a pointer to this value" a lot of the // Instances map construction below needs — Go has no address-of-literal // operator, so every *int field needs a local to take the address of // without this helper. func intPtr(v int) *int { return &v } // SnapshotPeriod is how often every box republishes its own // status/boxes/