Skip to content

Operations & observability

This page is what an operator reads from a running App: readiness, a support bundle, and local HTTP diagnostics. Nothing is sent anywhere — no collector, no telemetry.

auth: {
roles: {
member: { read: true, write: true },
operator: { read: true, operations: ['readiness', 'bundle'] },
},
},

operations is a grant of its own; no data or command grant implies it. 'readiness' opens GET /api/operations/readiness and 'bundle' opens GET /api/operations/support. Replies are Cache-Control: no-store; failures are a bare invalid_request (400), not_authorized (403) or unavailable (503). An App that signs people in is required: the standalone database browser serves neither route.

An aggregate ready flag and named checks, each an allowlisted state and code, never an exception or driver message:

Check Present Not ready or degraded
listener always —
sources always not_ready, connection_unavailable
policy with --policy not_ready, configuration_invalid
diagnostics with TABLEWALK_DIAGNOSTICS=1 degraded, diagnostics_degraded
pools once a PostgreSQL pool opens degraded, pool_saturated
jobs, events.delivery with --jobs degraded: backlog_pressure, work_failed; not_ready: worker_halted
effects when a command declares effects degraded: work_failed; not_ready: worker halted

Nothing probes a database; ready is not a capacity claim. Load balancer health checks use /api/app (or /api/whoami for an App that signs people in).

Readiness, plus HTTP metrics when diagnostics are on, plus a capacity section. It never contains App or connection names, paths, SQL, record values, credentials or the caller’s identity.

capacity answers “is it the process, the pool, or the database?”. Its counters are cumulative since start, so compare two bundles.

  • eventLoop: cumulative activeMs and idleMs.
  • pools: per PostgreSQL pool role (named by position in the config, never by name or URL): size, open, inUse, waiting, saturated, and cumulative checkouts, queued, timedOut, failed, waitBuckets, holdBuckets.
  • durationBoundsMs: the buckets’ bounds.

Reading the pool meters says which pattern means add pods, raise a pool, or look at the database.

Terminal window
TABLEWALK_DIAGNOSTICS=1 npx tablewalk --app ./myapp

Each response carries an X-Request-ID, and each request ends as one JSONL http.request event on stderr: time, id, method, a fixed route category (api.query, web, never the URL), status, duration and whether it completed. At shutdown an http.metrics summary adds latency buckets. A 200 can still carry a refused save, so judge outcomes by the write result, not the status. MCP is not instrumented. docs/observability.md has the full contract.

Failed jobs and webhook deliveries wait for an operator. There is no HTTP write surface; run these from tablewalk/maintenance:

Jobs Event deliveries
inspectJobs, inspectJobAttempts inspectEventDeliveries
redriveJobs redriveEventDeliveries
pruneJobs pruneEventDeliveries

A redrive puts failed or unknown work back in line and records the operator. A worker that halts stays halted until the process restarts. Stores are provisioned before start; see events and jobs.