The event feed

Per-namespace RSS of what happened to your deployments: deploys, updates, removals, and operational trouble. Strictly opt-in — a spec that says nothing publishes nothing.

Opting in

Three independent switches on the deployment spec, all off by default:

"feed": {
  "announce": true,          // publish registered / updated / removed
  "issues": true,            // publish operational trouble
  "expose": "/feed.xml"      // serve the namespace's feed on this deployment's routes
}
SwitchPublishes
announceThe deployment was registered, its spec was replaced or edited, or it was deregistered — with its public URLs in the entry, so a subscriber can go look.
issuesA VM that never became ready inside its boot timeout, a VM create the daemon refused, a static upstream going unhealthy (and recovering), and a cold start that timed out with a request waiting.
exposeNothing — it makes the feed reachable; see below.

Repeats fold: the same issue recurring within five minutes becomes one entry with a count and a fresh timestamp, so a crash-looping VM is one story, not two hundred items pushing everything else off the feed.

Reading a feed

# which namespaces have events
serverctl feed

# the events, newest first
serverctl feed team-a

# the RSS document itself
serverctl feed team-a --xml > feed.xml

On the admin API: GET /feeds lists namespaces with events; GET /feeds/team-a (or team-a.xml) is RSS 2.0, and ?format=json returns the same entries as structured data. Both sit on the view tier and behind the token namespace wall — a namespace token reads its own feed and no other, and a plain deployment-scoped token reads none (which namespaces exist is fleet information).

Making a feed public

The admin listener is loopback; the only door from the outside is expose. A deployment that carries "expose": "/feed.xml" serves its namespace's feed at that path on its own routes, through the proxy — after its own auth gate:

Any deployment in the namespace can be the avenue — a team might give its status page deployment the expose and leave every other spec silent.

The feed is a ring in memory — 200 events per namespace, and a restart starts it empty. Feed readers handle that shape gracefully (an empty feed reads as "nothing new"). The durable record is the job history and app-obs; the feed is the subscription view.

Entry anatomy

{
  "id": 7,                          // monotonic; the RSS <guid>
  "ts": 1722400000,                 // first occurrence
  "last_ts": 1722400120,            // latest, when folded
  "count": 3,
  "namespace": "team-a",
  "deployment": "web",
  "kind": "issue",                  // deployed | updated | removed | issue
  "title": "web: cold start timed out",
  "detail": "a request waited 120s and no VM became available"
}