# The instance-report contract (`/.carlos/instances`)

Some CARLOS apps are internally plural: one route on the platform, many
instances inside the app (Kass and its training instances, titogo and its
event routers). The platform's registry sees the route; only the app knows
its insides. This contract lets an app volunteer that knowledge to the
console. It is entirely optional — an app that doesn't implement it renders
in the console exactly as before.

## What the app exposes

```
GET /.carlos/instances
```

on its **public host** (the same host the platform routes to it). Response:

- Status `200`, body a **JSON array** of instance objects:

```json
[
  {"slug": "riders-2026", "status": "ready",    "region": "eu-west-1"},
  {"slug": "spring-camp", "status": "ready",    "region": "eu-west-1"},
  {"slug": "trial-run",   "status": "archived", "detail": "read-only since June"}
]
```

- `slug` (string, **required**): the instance's stable short name. Entries
  without a slug are dropped.
- `status` (string, recommended): one short word — the console renders it
  verbatim. Use the platform's route-status vocabulary where it fits
  (`ready`, `starting`, `failed`) plus whatever the app means (`archived`,
  `migrating`).
- `region` (string, optional): where the instance's data lives.
- `detail` (string, optional): one short human sentence.
- Unknown fields are ignored; the contract may grow additively.

Limits: at most **100 instances** and **64 KiB** of body are read; beyond
either, the report is treated as absent.

The endpoint must serve **non-secret operational metadata only**. It is
unauthenticated by design (the console calls through the public host, like
any visitor), so anything an app would not put on a status page does not
belong in it. Apps that want the console table implement the endpoint; apps
that don't, 404 — both are correct behavior.

## What the console does

- Polls the app's **first ready, non-static route that has not opted into
  hibernation** (routes sort by host). Hibernatable hosts are never polled:
  a poll could wake them, and the console must never wake an app to draw a
  table.
- Uses a **1.5 s timeout** and treats anything but a fast, valid `200` —
  connection refused, timeout, non-200, junk JSON, oversized body — as
  "not reporting". Not reporting is silent: the app page shows the ordinary
  route-level view, nothing more.
- **Caches per host for 30 s** (successes and failures alike), so page
  reloads don't drum on tenants.
- Renders the instance count and a slug/status/region/detail table in an
  "Instances" section on the app page.
