Deploy and operate
Documentation

Version Latest

Monitoring and self-healing

Health and readiness probes, Prometheus metrics, and selfguard, Govern's own anomaly detection, admission control and remediation playbooks, with the operator API, kill switch, drills and the rollout gate.

Probes and metrics

EndpointAuthenticationWhat
GET /healthznoneThe server and its database: {"status": "ok", "database": "ok"}. Liveness
GET /readyznoneReadiness, with the database pinged (2 s deadline) and email status
GET /readyz/selfguardnoneFails (503 selfguard_draining) only while selfguard drains the node; does not check the database. The Helm chart's readiness probe
GET /metricsNI_AUTH_METRICS_TOKEN or a super adminPrometheus metrics for every organisation
GET /metrics/selfguardthe sameBreak-glass status that works while the database is down
GET /metrics/selfguard/rollout-gatethe sameThe rollout gate, for deploy tooling

NI_AUTH_METRICS_TOKEN must be at least 32 bytes (openssl rand -base64 48). Scrape with it as a bearer token.

The probes, /metrics and /metrics/selfguard are exempt from admission control, so they answer while the node is overloaded.

Selfguard

Selfguard keeps each Govern process healthy on its own. It watches the process and the dependencies Govern needs (PostgreSQL first), detects anomalies, sheds load under pressure, repairs what it safely can, verifies that the repair worked, and escalates to operators when it did not. It is about Govern itself, not tenant workloads.

  • Air-gap first. No external calls. Alerts go to the log and, optionally, to a webhook you run.
  • Audited. Every automatic or operator action and every finding transition is written to the audit ledger in the same transaction as the change.
  • Database outages are the main case. Detection, healing and alerting run from memory; history is written to PostgreSQL in the background, buffered (up to 10,000 records) while it is down.
  • Safe by construction. Actions run under a mode, a cluster-wide kill switch, cooldowns, an hourly budget (12 per node), prechecks and post-action verification. Disruptive actions need an operator's approval.

It also runs in the data plane (keystone-bridge), the compliance plane (keystone-comply), the provisioner and the sandbox daemons, from memory and without history or approvals.

What it watches

AreaSignals
HTTPRequests, errors, error ratio, p50 and p99 latency, shed requests
Go runtimeGoroutines, heap, memory against GOMEMLIMIT or the container limit, GC pauses, scheduler latency
ProcessOpen file descriptors against the limit
Database poolIn use, idle, queueing time
PostgreSQL serverConnection slots in use, longest transaction, sessions idle in a transaction, lock waits, replication lag (one node samples every 30 s)
CanariesThe server fetches its own JWKS and OpenID discovery document over loopback every 30 s
DisksFree space of the diagnostics, temporary and data directories, and NI_AUTH_SELFGUARD_DISK_PATHS
CertificatesExpiry of the configured certificate files and NI_AUTH_SELFGUARD_CERT_FILES, re-read hourly
Background jobsHeartbeats, runs, durations, failures, crash loops and stalls of every supervised job
ClusterThe same finding on most nodes, lost nodes, silent nodes, and a new release that regresses against the previous one

Set GOMEMLIMIT or a container memory limit; without either, memory pressure cannot be measured.

Detectors and playbooks

RuleConditionPlaybook
runtime.memory_pressureMemory at 85% of the limit for 3 tickscapture diagnostics, release memory, shed batch, drain node
process.fd_exhaustion85% of file descriptors for 2 tickscapture diagnostics, recycle DB connections, shed batch, drain node
db.pool_saturationPool 90% in use and queueingshed batch, capture diagnostics
db.server_connections90% of PostgreSQL connection slots, 2 samplesrecycle DB connections, capture diagnostics
db.idle_in_transactionA Govern session idle in a transaction for 10 minterminate idle transactions (approval)
probe.failing/databaseThe database probe failed 3 timesrecycle DB connections
http.slo_fast_burnAvailability budget burning 14.4 times too fast over 1 h and 5 mincapture diagnostics
http.latency_anomalyp99 well above the learned baseline for that hour of the weekshed batch, capture diagnostics
job.stalledA job missed its heartbeat or overranrestart the job
disk.pressureBelow 15% and 10 GiB free (warning), or below 1 GiB (critical)prune diagnostics
cert.expiryA certificate expires within 30 days (critical within 7)operators

The full list, with thresholds, is in the operator runbook shipped with each release. Baselines warm up first and never fire before that.

Admission control

The outermost HTTP handler limits concurrency adaptively (starting at 256, between 32 and 4096) and classifies requests:

ClassRequestsAdmitted while in flight is below
exempt/healthz, /livez, /readyz, /readyz/selfguard, /metrics, /metrics/selfguardalways
critical/.well-known/, token, revoke, userinfo and introspection endpoints, /api/v1/auth/, /api/v1/authz/check, the selfguard API125% of the limit
normaleverything else100% (90% in protect mode)
batchpaths with a segment such as export, exports, download, report, bulk, import, reindex70% (0% in protect mode)

Once in-flight requests reach half the limit, one organisation may hold at most a quarter of it (tenant bulkheads). A rejected request gets 503 (node overloaded) or 429 (tenant share exceeded) with Retry-After and X-Keystone-Shed. Long-lived streams give their slot back once they start. NI_AUTH_SELFGUARD_ADMISSION=measure measures without rejecting.

Operate selfguard

All routes are for super admins. Bodies are JSON; every change needs a reason or note, recorded in the audit event.

export KS=https://<your Govern host>
alias ks='curl -sS -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json"'

ks $KS/api/v1/admin/selfguard/nodes
ks "$KS/api/v1/admin/selfguard/findings?state=open"
ks "$KS/api/v1/admin/selfguard/actions?limit=20"

If the API answers 401 during an incident, the database is probably down and sessions cannot be checked. Use the break-glass status with the metrics token:

curl -sS -H "Authorization: Bearer $NI_AUTH_METRICS_TOKEN" $KS/metrics/selfguard
TaskRequest
Acknowledge a findingPOST /api/v1/admin/selfguard/findings/{id}/acknowledge {"note": "..."}
Approve or reject a disruptive actionPOST /api/v1/admin/selfguard/actions/{id}/approve or /reject {"note": "..."}, within the approval window (15 min)
Pause every automatic action (kill switch)PATCH /api/v1/admin/selfguard/settings {"actions_paused": true, "paused_reason": "..."}
See what it would do without actingPATCH .../settings {"mode": "dry_run", "reason": "..."}; back with "enforce"
Run an action on a node nowPOST /api/v1/admin/selfguard/actions {"node_id", "action", "subject"?, "note"}
Rehearse detection and alertingPOST /api/v1/admin/selfguard/drills {"node_id", "rule", "severity", "duration", "live"?, "note"}

The kill switch reaches every node within one tick (10 s). Detection and alerting continue while actions are paused. A drill raises a finding whose message starts with [DRILL]; its playbook runs as a dry run unless "live": true. Run a drill after enabling selfguard, after changing the alert webhook, and at on-call handover.

Disruptive actions:

  • drain_node makes /readyz answer 503 for 3 minutes, so load balancers route around the node. It is never proposed for the only active node or while another node drains. Approve when the node is clearly sick and the others have headroom.
  • terminate_idle_transactions ends Govern's own sessions idle in a transaction for over 10 minutes. Before approving, look at what they hold (pg_stat_activity); reject when it is a migration or a maintenance session. Then set idle_in_transaction_session_timeout on Govern's database role.

Diagnostic bundles (goroutines.txt, heap.pprof, status.json) are written under NI_AUTH_SELFGUARD_DIAGNOSTICS_DIR; mount a volume there to keep them across restarts.

Alerts

Alerts go to the structured log and, when NI_AUTH_SELFGUARD_ALERT_WEBHOOK_URL is set, are posted as JSON with kinds finding.opened, finding.escalated, finding.resolved, action.approval_required, action.failed, action.unverified and remediation.exhausted. Each request carries X-Keystone-Timestamp and X-Keystone-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + body)>; reject timestamps older than a few minutes. Delivery is retried up to 5 times.

The rollout gate

curl --fail -sS -H "Authorization: Bearer $NI_AUTH_METRICS_TOKEN" \
  "$KS/metrics/selfguard/rollout-gate?release=$TAG&min_soak=10m&strict=true"

It answers {"release", "proceed", "reasons", ...} and, with strict=true, 412 while: no node reports the release; a node running it is not ready or has an open critical finding; it has run for less than min_soak; it regresses against the previous release (error ratio more than twice, or p99 more than 1.5 times, the baseline's, with at least 200 requests on each side); or a critical cluster-wide finding is open. Use it as an Argo Rollouts analysis or a Helm post-upgrade hook. Govern does not roll back by itself; the gate lets your tooling do it. The Helm chart sets NI_AUTH_SELFGUARD_RELEASE to the image tag.

Settings

All optional. An invalid value stops the start with a message naming it.

VariableDefaultMeaning
NI_AUTH_SELFGUARD_ENABLEDtruefalse turns detection, healing and shedding off; jobs stay supervised
NI_AUTH_SELFGUARD_MODEenforceobserve, dry_run or enforce, until an operator stores a mode
NI_AUTH_SELFGUARD_TICK10sDetection interval
NI_AUTH_SELFGUARD_MAX_ACTIONS_PER_HOUR12Automatic actions per node per hour
NI_AUTH_SELFGUARD_AUTO_APPROVEemptyDisruptive actions that run without approval, for example drain_node
NI_AUTH_SELFGUARD_APPROVAL_TTL15mApproval window
NI_AUTH_SELFGUARD_DIAGNOSTICS_DIR$TMPDIR/keystone-selfguardDiagnostic bundles
NI_AUTH_SELFGUARD_DIAGNOSTICS_KEEP10Bundles kept
NI_AUTH_SELFGUARD_DRAIN_DURATION3mHow long a drain reports not ready
NI_AUTH_SELFGUARD_RETENTION_DAYS90History retention
NI_AUTH_SELFGUARD_SLO_AVAILABILITY0.999Availability objective of the burn-rate rules
NI_AUTH_SELFGUARD_MEMORY_PRESSURE_RATIO0.85Memory pressure threshold
NI_AUTH_SELFGUARD_ALERT_WEBHOOK_URL, NI_AUTH_SELFGUARD_ALERT_WEBHOOK_SECRETemptyAlert webhook and its HMAC secret (at least 32 bytes)
NI_AUTH_SELFGUARD_ADMISSIONonon or measure
NI_AUTH_SELFGUARD_ADMISSION_INITIAL_LIMIT, _MIN_LIMIT, _MAX_LIMIT256, 32, 4096Adaptive limit bounds
NI_AUTH_SELFGUARD_ADMISSION_STATIC_LIMIT0Above 0 pins the limit
NI_AUTH_SELFGUARD_ADMISSION_TENANT_SHARE0.25One organisation's share once bulkheads engage
NI_AUTH_SELFGUARD_RELEASEbuild versionThe release the rollout gate compares
NI_AUTH_SELFGUARD_DISK_PATHSemptyExtra filesystems to watch, label=/path
NI_AUTH_SELFGUARD_CERT_FILESemptyCertificate files to watch, label=/file.pem

Give server pods a terminationGracePeriodSeconds of at least 30 (the chart uses 60), the bridge at least 50 and the compliance plane at least 45, so their last flushes finish on shutdown.

Metrics to alert on

MetricAlert when
keystone_selfguard_findings_active{rule,severity}Any critical finding is active
keystone_selfguard_drainingA node drains
keystone_selfguard_journal_dropped_totalIt increases: history was dropped during a long database outage
keystone_selfguard_shed_requests_total{priority,reason}Normal or critical traffic is shed
keystone_selfguard_breaker_state{breaker}A breaker is open (2)
keystone_email_delivery_healthyIt is 0 for 10 minutes

Not covered yet

  • No console page for selfguard beyond the platform area's self-healing page; the API is complete.
  • No automatic rollback of a bad release: the rollout gate's 412 lets your tooling do it.
  • No circuit breaker per model provider in the model gateway yet.

Last reviewed 17 October 2026View as Markdown

Monitoring and self-healing · Docs · Neural Inverse