Status app
Not monitorednormal@atrium/statusThis app: the health of every agency system, plus the documentation for each one.
checked never
Checks
Status API
This dashboard is down, so nothing else is being watched either.
Set NEXT_PUBLIC_STATUS_URL to the deployed status URL so the app can watch itself from the outside.
What it is
Two surfaces, deliberately kept apart. Status is live and autonomous: a cron hits one endpoint, every monitor runs, results and incidents land in Upstash. Systems is the documentation: what each thing is, how it works, what to do when it breaks.
Nothing here is hand-updated to stay current. The only file a human maintains is the system's own definition, and adding a system is one file.
Adding a system
- Copy
config/systems/_template.tstoconfig/systems/<id>.ts. - Fill in the documentation and the monitors.
- Add one line to
config/systems/index.ts.
That is the whole procedure. The dashboard, the per-system page, the cron, the uptime maths and the public JSON API all pick it up with no further changes. Monitor ids must be unique across all systems — they are the storage keys — and the registry validates that at import time.
How checks actually run
/api/cron/check runs every enabled monitor in parallel, classifies each result as up / degraded / down, writes the run to Upstash, and opens or closes an incident when the status flips. The request is rejected unless it carries CRON_SECRET.
A page view never probes directly. What it can do is notice the stored data has gone stale and schedule a sweep after the response is sent — so looking at the board can refresh it, but never slows it down, and the numbers on screen are always the ones that were measured.
Scheduling, and the Hobby-plan problem
Vercel's Hobby plan allows exactly one cron a day, which is a report, not a health check. Three layers cover it, in increasing order of reliability:
- Vercel Cron, daily, from
vercel.json. It is the floor, and it is what Hobby permits. - Stale-triggered sweeps. Opening the dashboard or calling
GET /api/healthrefreshes the
data in the background when the last sweep is older than SWEEP_STALE_AFTER_MINUTES (10 by default). A short lock in Upstash means ten people opening the page cause one sweep between them, not ten.
- A GitHub Actions heartbeat at
.github/workflows/status-sweep.yml, every 10 minutes,
calling the same endpoint with the same secret. The repository is public, so those minutes are free. It needs two repository secrets: STATUS_URL and CRON_SECRET.
Layer 2 alone keeps the board honest for anyone looking at it. Layer 3 is what catches an outage while nobody is looking, which is the only reason a status page exists. On a Pro plan, set the real schedule in vercel.json and delete the workflow.
Storage
Upstash Redis over its REST API, which works from any runtime:
atrium:monitor:{id}:last— the most recent runatrium:monitor:{id}:runs— a capped list used for uptime and the sparklinesatrium:incident:{monitorId}— the currently open incident, when there is oneatrium:incidents:log— closed incidents, newest first
Without Upstash credentials the app falls back to an in-memory store so local development works unconfigured. That store is per-process and disappears on restart, which is fine locally and useless in production — the deployment must have the credentials.
What is local-only
The repo view runs turbo to typecheck, lint, test and build workspaces on demand. That cannot work on Vercel — there is no toolchain and no working tree in a serverless function — so those endpoints refuse outside development. The deployed app shows the snapshot committed by bun run checks:snapshot instead, with the commit and timestamp it came from.
When it breaks
Symptom
Every monitor reads 'unknown'
Check
Whether the cron has ever run: /api/cron/check in the Vercel logs, and UPSTASH_REDIS_REST_URL / _TOKEN.
Fix
Set the Upstash credentials and CRON_SECRET, then trigger the cron once manually.
Symptom
Cron returns 401
Check
CRON_SECRET in the deployment environment against the value Vercel sends.
Fix
Set CRON_SECRET in Vercel; the schedule in vercel.json is what invokes it.
Symptom
A monitor is red but the system is fine
Check
Its expectStatus — some endpoints legitimately answer 403 or 405.
Fix
Widen expectStatus in that system's definition. A monitor that cries wolf is worse than no monitor.
Configuration
| variable | status | purpose |
|---|---|---|
UPSTASH_REDIS_REST_URLVercel project env | missing | Where run history and incidents are stored.Without it the app silently uses an in-memory store that resets on every cold start. |
UPSTASH_REDIS_REST_TOKENVercel project env | missing | Auth for the same. |
CRON_SECRETVercel project env + GitHub repository secret | missing | Shared secret the sweep endpoint requires.Vercel sends it as a Bearer token on scheduled invocations; the GitHub Actions heartbeat sends the same value. |
SWEEP_STALE_AFTER_MINUTESVercel project env | missing | How old the data may get before a page view refreshes it in the background.Defaults to 10. Lower means fresher and more outbound requests; higher means a quieter board that can be behind. |
NEXT_PUBLIC_WEBSITE_URL / NEXT_PUBLIC_GRADER_URLVercel project env | missing | Production URLs the monitors point at.The website falls back to https://atrium.agency; the grader monitor stays paused until its URL is set. |
Where to start reading
System registry
apps/atrium.status/config/systems/index.tsMonitor runner
apps/atrium.status/lib/health/runner.tsStorage adapters
apps/atrium.status/lib/health/store.tsCron endpoint
apps/atrium.status/app/api/cron/check/route.ts
In the repo
path
apps/atrium.statusversion
files
lines
routes
depends on
Incident history
No incidents recorded for this system.