Operations Runbook
Operations Runbook — First-Incident Playbooks
This runbook is the first-response reference for operators of the LanOnasis platform. It covers the three most common incident classes:
Every playbook includes the observable checks to run first, the actions to take, and the escalation boundary where a human owner must take over. Keep this page source-derived: when the underlying contract changes (health payload shape, rate-limit defaults, key endpoints), update this page in the same change.
Platform entry points (from Operating the Platform):
- API Gateway:
api.lanonasis.com- MCP Core:
mcp.lanonasis.com(SSE/HTTP,/healthand/api/v1/health)- Auth Gateway:
auth.lanonasis.com
Severity definitions
| Severity | Meaning | Response target |
|---|---|---|
| SEV3 | Single user / key affected, service otherwise healthy | Same business day |
| SEV2 | Partial service degradation (one surface: MCP, REST, or auth) | < 1 hour |
| SEV1 | Platform-wide outage or suspected credential exposure | Immediate, page on-call |
1. API key rotation
Trigger: suspected key leak, scheduled rotation policy, offboarding, or a vendor key compromised in an upstream .env.
Observable checks (run first)
# List API keys / projects (CLI surface)
onasis api-keys list
# Inspect MCP tool availability for the key manager
onasis mcp tools | grep -i key
# Confirm which keys are active and their creation dates
onasis api-keys get <key-id>
- If you see
RateLimitor401 Unauthorizedon a previously working key, treat it as a possible compromised or expired key before assuming platform failure. - Check the audit log surface for unusual access patterns (
onasis api-keys security-events).
Actions
- Create a new key before revoking the old one (never leave the service without a valid credential):
Ifset -euo pipefailKEY_JSON=$(onasis api-keys get <old-key-id> --json)EXISTING_SCOPES=$(jq -er 'if (.data.scopes? | type) == "array" then .data.scopeselif (.data.permissions? | type) == "array" then .data.permissionselif (.scopes? | type) == "array" then .scopeselif (.permissions? | type) == "array" then .permissionselse emptyend| select(length > 0)| join(",")' <<<"$KEY_JSON")onasis api-keys create --name "rotation-<timestamp>" --scopes "$EXISTING_SCOPES"
onasis api-keys get --jsondoes not return a non-emptyscopesorpermissionsarray for the current CLI/API contract, stop and require the operator to provide the intended replacement scope set explicitly before creating the new key. - Migrate consumers to the new key by replacing the actual API key value they send in the
X-API-Keyheader or in the bearer token carried by theAuthorizationheader, plus any environment variables or secret-manager entries that populate those headers. Keep handle/alias migration for upstream vendor credentials only (see Vendor Key Management). - Retire the old key based on incident type:
- Scheduled rotation: keep the old key only for the agreed verification window, confirm consumers have switched, then revoke it.
- Suspected compromise: once containment is ready and consumers have an emergency replacement path, disable or revoke the old key immediately — do not wait out a routine verification window.
onasis api-keys delete <old-key-id> - For upstream vendor keys, rotate inside the centralized key manager, not by editing
.envfiles. Remove any provider keys from app configs after rotation (see Vendor Key Management). - Record the rotation in the audit trail and close out the ticket with the new key ID and the revoke time.
Escalation boundary
- Operator: anyone with key-manager access can perform the rotation.
- Escalate to SEV1 / security owner if: the leaked key was a
service_role-equivalent ormaster_*key, if you cannot confirm which consumers used the old key, or if the leak appears to include production database or vendor credentials. Do not silently rotate in that case — coordinate revocation windows with all consumers first.
2. MCP health degradation
Trigger: MCP tool calls start failing or timing out; clients report degraded tool responses; /health returns 503.
Observable checks (run first)
# The health endpoint is NOT rate-limited by design — use it as the ground truth
curl -sS --connect-timeout 5 --max-time 15 https://mcp.lanonasis.com/health
curl -sS --connect-timeout 5 --max-time 15 https://mcp.lanonasis.com/api/v1/health
The health handler returns 200 with status healthy, or 503 with status degraded/unhealthy, and a services block:
{
"status": "degraded",
"services": {
"database": "connected",
"cache": "error",
"mcp": "running"
},
"uptime": 12345,
"version": "1.0.0",
"timestamp": "2026-08-08T00:00:00.000Z",
"response_time_ms": 42,
"memory_usage": { "used": 123, "total": 256, "percentage": 48 }
}
services.database and services.cache may each report connected, error, or disconnected depending on the failing dependency.
Read the services block to localize the failure:
database: error|disconnected→ database connectivity problem (health check runs a fast DB probe with a 2 s timeout).cache: error|disconnected→ Redis/cache problem (degraded but may still serve requests).status: unhealthy(whole payload failed) → process-level failure; check process liveness and logs.
Actions
- Confirm scope: is it MCP only, or REST too? A database outage affects both; a MCP-process issue affects only MCP.
- For database issues: check database connection pool, credentials, and network path. Verify the DB handler reports
healthyagain after remediation. - For cache issues: verify Redis connectivity and restart the cache service; MCP should recover without full restart.
- For process issues: inspect MCP Core logs, restart the service, and re-check
/healthuntilstatus: healthywithservices.database: connected. - Re-run a real tool call (
onasis mcp tools, then a read-only tool) to confirm functional recovery — presence of a 200 on/healthis not the same as a working tool round-trip.
Escalation boundary
- SEV3 / operator: cache degraded or a single tool failing with healthy DB.
- SEV2: database
error/disconnected, or/healthreturning 503 for more than a few minutes. Alert the platform on-call. - SEV1: full
unhealthyacross both REST and MCP with no local remediation path within 30 minutes — escalate to the infra owner; consider failover if the environment has one.
3. Quota / rate-limit handling
Trigger: a client receives 429 Too Many Requests, or a tenant hits quota limits.
Observable checks (run first)
The MCP server rate limiter responds with:
{
"error": "Too many requests",
"retryAfter": 900
}
retryAfteris in seconds and equals the configured window (RATE_LIMIT_WINDOW_MS / 1000).- With
standardHeaders: true, the response includesRateLimit-*headers (limit, remaining, reset) — inspect those to see the actual budget. /healthis explicitly excluded from rate limiting — a 429 on health indicates a different problem (e.g. a proxy or WAF layer).
Default budget (from apps/lanonasis-maas/src/config/environment.ts unless overridden):
| Setting | Default | Meaning |
|---|---|---|
RATE_LIMIT_WINDOW_MS | 900000 (15 min) | Rolling window length |
RATE_LIMIT_MAX_REQUESTS | 100 | Max requests per window |
AUTO_SUSPEND_RATE_LIMIT_VIOLATIONS | 50 | Violations after which auto-suspend engages |
Actions
- Identify the caller — rate limits are per-IP / per-key depending on the layer. Ask the client for the exact 429 payload and
RateLimit-*headers, not just "it failed". - Distinguish quota vs rate limit:
- Rate limit (429, short
retryAfter) → burst traffic. Retry afterretryAfterwith backoff, or batch requests. - Quota (4xx quota error, or
usagereporting) → monthly/plan budget exhausted. Checkonasis api-keys usageand the plan tier.
- Rate limit (429, short
- Legitimate bursts: obtain platform-owner approval before changing shared
RATE_LIMIT_MAX_REQUESTSorRATE_LIMIT_WINDOW_MSdefaults. Prefer a tenant-scoped override when available, then redeploy the service and confirm the new headers. - Abuse / runaway client: keep the limit, block the offending key, and contact the client. Auto-suspend at 50 violations should have engaged — verify the suspension state.
- Checklist before closing: new limit is deployed, client confirms a 200 on a real request, and the
RateLimit-*headers show headroom.
Escalation boundary
- Operator: adjusting limits or unblocking a legitimate tenant key.
- SEV2: a single large tenant is blocked and the fix requires changing shared rate-limit defaults (affects all tenants) — escalate to the platform owner before changing global limits.
- SEV1: a distributed attack is exhausting the shared rate-limit budget (all tenants affected) — escalate to security/infra, do not keep raising limits to absorb it.
Related
- Operating the Platform — non-obvious operational facts
- Vendor Key Management — key manager flows
- MCP Production Server — MCP deployment
- Central Auth Gateway — authentication flows