MainSDK
Search⌘K
Interfaces

Reliability and operations

Health and status page

status

Serve health probes, drain cleanly on shutdown, watch SLO burn and resource pressure, and register a check for each dependency.

Building for 0.4ships as a beta APIPhase 1 · Core, public betaSpec healthFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

status gives your app health probes, a check for each dependency, a clean drain on shutdown and SLO burn alerts. Register a check for each thing you depend on, and MainSDK reports the cached state.

At a glance

status:
  driver: builtin
health:
  live: /healthz
  ready: /readyz
  critical: [db]                       # dependencies that make the app unready
  token: secret:HEALTH_TOKEN           # optional: guards the detailed report
  shutdown:
    deadline: 25s
    drain-delay: 5s
slo:
  availability: 99.9%
  exclude: ["GET /healthz"]            # routes left out of the readings
  alert: { to: [log, telemetry] }
resources:
  critical: ["POST /api/checkout/**"]  # never paused or shed
  shed: { loop: 500ms }
  nice: 10                             # 0 to 19
  retry-after: 5s
  max-pause: 5m

Calls

  • status.check(name, fn, { every?, timeout?, critical? })
  • resources.snapshot()
  • resources.state()
  • resources.onPressure(fn)
  • cgroupCores(read)
  • cgroupMemory(read)
  • isServerless(env)
  • nodeSources(opts)
  • parseResources(sec)
  • validateResources(sec)
  • parseHealth(sec, env)
  • validateHealth(sec, env, { known })
  • new Slo(opts)
  • parseSlo(sec)
  • validateSlo(sec)
  • resources(object: snapshot, state, onPressure)
  • realTimers(const)

Guarantees

Whatever the driver.

  • Health checks per dependency
  • Probes read cached state only and never hang on a slow dependency

Drivers

2 drivers. Select one to see its line in main.yaml.

main.yamlBuilt in (default)
status:
  driver: builtin

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-HEALTH-0005Checkmainsdk check, before deployAn slo value is invalid: availability outside 90% to 99.999%, or a latency that isn't a duration
MAIN-HEALTH-0006Checkmainsdk check, before deployhealth.critical names a dependency the app doesn't configure, or names mainsdk-cloud or tuf; or drain-delay plus 2s is not less than deadline
MAIN-RESOURCE-0003Checkmainsdk check, before deployA resources value is invalid: a percentage outside 10% to 100%, a loop under 20ms, a shed threshold not above its pressure threshold, a critical pattern that doesn't parse, or nice outside 0 to 19
MAIN-HEALTH-0001RuntimeError responses and logsThis instance is not ready: starting, draining, or a critical dependency is down (503 on /readyz)
MAIN-HEALTH-0002RuntimeError responses and logsA request arrived after the app stopped accepting work (503 with Retry-After and Connection: close)
MAIN-HEALTH-0003RuntimeError responses and logsThe shutdown deadline cut work that was still running; the record lists what was released
MAIN-HEALTH-0004RuntimeError responses and logsA probe threw or timed out; its dependency is marked down (logged once per change)
MAIN-HEALTH-0010RuntimeError responses and logsThe availability error budget is burning faster than a burn rule allows
MAIN-HEALTH-0011RuntimeError responses and logsThe latency budget is burning faster than a burn rule allows
MAIN-RESOURCE-0001RuntimeError responses and logsA new non-critical request or realtime connection was shed (503 with Retry-After); logged at most once a minute with a count
MAIN-RESOURCE-0002RuntimeError responses and logsThe instance entered or left pressure or shed, with the reasons and the values
MAIN-RESOURCE-0004RuntimeError responses and logsnice couldn't be applied; the app starts anyway (logged once)
MAIN-RESOURCE-0005RuntimeError responses and logsBackground work waited longer than max-pause; it now runs one at a time until pressure ends
MAIN-RESOURCE-0006RuntimeError responses and logsThe V8 heap limit is above the container's memory limit, so the process can be killed before the heap fills

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
App status page and status.incident (not built; the incident controls are the separate incident interface)Phase 20.6Not specified yet
Remote dependency state through health()Phase 10.4fail-static#dependency-status
health and slo sections in main.yamlPhase 10.4health#contract
/healthz and /readyz from every adapterPhase 10.4health#probes
Custom readiness probes with status.checkPhase 10.4health#status-check
Graceful drain on SIGTERMPhase 10.4health#drain
Shutdown deadline and release of cut workPhase 10.4health#deadline
SLO windows from the request logPhase 10.4health#slo-windows
Error-budget burn alertsPhase 10.4health#slo-alerts
Probe state and burn in mainsdk infoPhase 10.4health#info
CPU, event-loop delay and memory sampled against the container's limitsPhase 10.4resources#sampler
Background work paused and caches trimmed under pressurePhase 10.4resources#pause-background
Non-critical requests shed with 503 and Retry-AfterPhase 10.4resources#shed
BurnPhase 10.4alerts#sources

Quick start

main.yaml
schema: 1
health:
  critical: [payments]   # not ready while this is down
app/status.ts
import { status } from "mainsdk";

status.check("payments", async () => true, { critical: true });

Replace the body with a ping of your provider. The check returns true when the dependency is healthy. It runs in the background, never on a request, so a hung dependency never hangs a probe.

Configuration

main.yaml
health:
  live: /healthz
  ready: /readyz
  critical: [db]               # dependencies whose "down" makes this instance not ready
  every: 10s
  timeout: 2s
  token: secret:HEALTH_TOKEN   # in production, the detail needs this bearer token
  shutdown:
    signals: [SIGTERM, SIGINT]
    deadline: 25s              # from the signal to exit, everything included
    drain-delay: 5s            # report not ready and keep serving; 0 in dev
slo:
  availability: 99.9%          # share of requests that don't fail with a 5xx
  window: 30d
  exclude: [/healthz, /readyz]
resources:
  every: 1s
  hold: 5s                     # a reading must stay over a threshold this long
  pressure: { cpu: 80%, loop: 100ms, memory: 80% }
  shed: { loop: 500ms }        # off unless you add it
  critical: ["POST /api/checkout/**", "job:billing.*"]
  retry-after: 5s
  max-pause: 5m
KeyWhat it does
health.criticalDependencies that make this instance not ready. MainSDK Cloud and TUF can never be listed
health.shutdown.deadlineThe most time from the signal to exit. Work still running is cut, released for retry and reported
slo.availabilityThe target. The latency target is latency_p95 (a duration such as 300ms, 95% of requests within it); set it in main.yaml next to it. Error-budget burn is logged with a code and exported as a metric
resources.pressureThresholds for pressure: background jobs and workflow runs wait, and /readyz says degraded
resources.shedThresholds for shed: new non-critical requests get 503 with Retry-After. Needs a section; no default
resources.criticalRoutes (METHOD /path/**) and action:, job: or workflow: names that still run under pressure and while shedding
resources.max-pauseWork that waited this long runs one at a time, so it slows down but never starves

The values shown for resources are the defaults, except shed and critical.

/healthz answers 200 while the process can respond. /readyz answers 200 with ready or degraded, and 503 with starting, draining or not-ready plus MAIN-HEALTH-0001. It reads cached state. Outside production the body lists every dependency; in production it does so only with the bearer token. On SIGTERM the app reports not ready, keeps serving for drain-delay, then stops taking work, drains requests, jobs, workflow steps and realtime streams, and flushes logs and audit. A resource pressure state never makes /readyz unready, only degraded.

A bad slo value fails mainsdk check with MAIN-HEALTH-0005; a bad critical name or a drain delay too close to the deadline fails with MAIN-HEALTH-0006; a bad resources value fails with MAIN-RESOURCE-0003.

What you get

  • /healthz and /readyz from every server adapter, with no route to write.
  • Circuit breaker states from the network layer listed as dependencies.
  • Request counting for SLOs: a 5xx is bad, a 4xx is good, under 2 microseconds a request.
  • Graceful shutdown in a fixed order inside the deadline, and mainsdk status for the current state.

Examples

Check a partner API

app/status.ts
import { http, status } from "mainsdk";

status.check("partner-rates", async () => {
  const res = await http.fetch("https://api.partner.com/v1/health");
  return res.ok;
});

Allow the host your check calls with Network permissions. A check that throws, returns false or times out marks its dependency down. A dependency that is not in health.critical shows as down in the detail and /readyz stays 200.

See the state of every dependency

health() lists the cached state of every probe, circuit breaker, the resource watcher and the disk. It never calls a driver.

app/api/health/route.ts
import { health } from "mainsdk";

export async function GET() {
  const parts = health();
  const down = parts.filter((d) => d.state === "down");
  return Response.json({ ok: down.length === 0, parts }, { status: down.length ? 503 : 200 });
}

Each entry has name, state (ok, degraded or down), since (RFC 3339), critical and sometimes detail. Run npx mainsdk status for the same list in your terminal, one line per dependency with its state. Add --json for machine-readable output. Registered probes appear once the app has started.

Trim caches under pressure

app/cache.ts
import { resources } from "mainsdk";

const localCache = new Map<string, string>();   // the app's own in-memory cache
const stop = resources.onPressure(() => localCache.clear());

localCache is your own. The hook runs when the instance enters pressure or shed. resources.state() returns ok, pressure or shed, resources.pressure is true while background work waits, and resources.snapshot() returns the state, reasons (cpu, loop, memory), cpu, loop and memory readings and since. onPressure returns a function that removes the hook.

Reference

Calls beyond the examples above. Every parse call reads a section into settings with defaults; every validate call returns the issues that mainsdk check reports.

CallWhat it does
resources(object)The resource watcher: resources.snapshot(), resources.state() and resources.onPressure(fn).
parseResources(sec)Reads a resources section. Invalid values fall back to the defaults.
validateResources(sec)Returns the issues in a resources section, each reported as MAIN-RESOURCE-0003.
cgroupCores(read)Returns the CPU quota in cores from cgroup v2 cpu.max, else v1, or null when unlimited or absent. read returns a file's text or undefined.
cgroupMemory(read)Returns the container memory limit from cgroup v2 memory.max, else v1, or null when unlimited or absent.
nodeSources(opts)Returns the real readings: Node's counters, V8, the cgroup files and the environment. Takes { read?, env? }.
isServerless(env)Returns true when the environment names a serverless platform, where an instance freezes between calls.
parseHealth(sec, env)Reads a health section. The drain delay is 0 in dev.
validateHealth(sec, env, { known })Returns the issues in a health section (MAIN-HEALTH-0006): a critical dependency that is MainSDK Cloud or TUF, or one the app does not configure.
new Slo(opts)Counts good, bad and slow requests in per-minute buckets and alerts on multi-window error-budget burn. Takes { config, log?, now?, timers?, metrics? }.
parseSlo(sec)Reads an slo section. A bad value is left out and reported by check.
validateSlo(sec)Returns the issues in an slo section (MAIN-HEALTH-0005).
realTimers(const)Timers that never keep the process alive. Tests pass a fake clock's timers instead.

Errors and checks

Runtime: MAIN-HEALTH-0001 (not ready, 503 on /readyz), 0002 (a request after the app stopped accepting work), 0003 (the deadline cut work), 0004 (a probe threw or timed out), 0010 and 0011 (the availability or latency budget burns too fast). MAIN-RESOURCE-0001 (a request was shed, 503 with Retry-After), 0002 (the state changed), 0004 (nice could not be applied), 0005 (background work waited past max-pause), 0006 (the heap limit is above the container's). Checks: MAIN-HEALTH-0005, MAIN-HEALTH-0006 and MAIN-RESOURCE-0003. See check codes and Error codes.

Swapping drivers

The status driver key (builtin or provider) belongs to the planned status page. Nothing reads it today: probes, drain, SLOs and resources have no driver.

Testing

createTestApp gives you the app with a testing clock, so probes and the SLO run without waiting. Read the probes from the app's health layer.

app/status.test.ts
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp } from "@mainsdk/testing";

it("is not ready while a critical check is down", async () => {
  const app = createTestApp({ yaml: "schema: 1\nproject: prj_test\nhealth:\n  critical: [payments]\n" });
  app.health.check("payments", async () => false, { critical: true });
  await app.health.start();
  const res = app.health.handle(new Request("http://x/readyz"));
  assert.equal(res?.status, 503);
});

The test pattern follows packages/sdk/test/health.test.ts, which also drives shutdown with fake signals and fake workers. The calls above ran against the repository.

Security and privacy

  • Health checks and resource watching are free in your app.
  • In production the detail needs the health.token bearer token. Keep check results free of personal data anyway.
  • MainSDK's own status page, for MainSDK Cloud and TUF delivery, is separate from yours and hosted apart from the Cloud it reports on.

FAQ

Is this the MainSDK status page? No. status is for your app. MainSDK publishes its own page for the Cloud and TUF delivery.

Does my app go down if MainSDK Cloud does? No. No MainSDK service is ever in your request path, and MainSDK Cloud can never be a critical dependency. See Fail static.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet