Incident Triage Playbook
On-call playbook for OGuardAI alerts with step-by-step response procedures
On-call playbook for OGuardAI alerts. Identify the alert, follow the matching section.
Severity Definitions
| Severity | Response Time | Alerts |
|---|---|---|
| critical | 15 min | PromptSecuritySpike |
| warning | 30 min | HighErrorRate, HighTransformLatency, OutputGuardSpike, RateLimitSaturation |
| info | Next business day | NoTraffic, EntityDetectionSpike |
OGuardAIHighErrorRate
Trigger: rate(guardai_errors_total[5m]) > 0.1 for 2 min.
- Check logs for failing endpoint:
kubectl logs -l app.kubernetes.io/name=oguardai -c server --tail=200 | grep ERROR journalctl -u oguardai-server --since "10 min ago" --priority=err # systemd - Check detector sidecar health:
kubectl exec deploy/guardai -- curl -sf http://localhost:9090/health - If detector is down, restart:
kubectl rollout restart deployment/guardai - If errors are on
/v1/rehydrate, check for expired session keys. See key-rotation runbook.
guardai_errors_total is a single unlabelled counter with no per-code breakdown, so read the code
off the structured log: each failed request logs one request failed event carrying error_code,
status, and request_id. The 503 codes mean a dependency is down rather than that the caller sent
bad content. Most are retriable once the dependency is back; strict audit needs the extra commit-state
check below.
| Code | What could not run | Where to look |
|---|---|---|
GUARDAI_OUTPUT_GUARD_UNAVAILABLE | The output scan. The response was never judged, so this is not a verdict on the content | The output NER scanner. A policy naming an NER-backed type in detection.required_for needs detector.scan_output_with_ner on with detector.mode both or advanced and a reachable detector, or every response under it fails closed |
GUARDAI_DOCUMENT_EXTRACTION_UNAVAILABLE | The PDF extraction sandbox could not be started, or its pipes were unavailable. The document is not implicated | The server log; the fixed 503 body deliberately carries no detail. After an in-place binary replacement on a host without procfs, restart the service |
GUARDAI_REPLAY_STORE_UNAVAILABLE | The replay check, with replay_backend: redis. The continuation was rejected, never accepted | Redis reachability |
GUARDAI_AUDIT_UNAVAILABLE | The strict audit write. The response never acknowledges unaudited success, but the mutation may be uncommitted, committed, or commit-indeterminate | Disk capacity, the stable trusted parent of audit.file.path, leaf type, owner, link count, mode 0600, queue pressure, and the dedicated audit.strict_max_in_flight admission bound; exhausted strict-audit admission capacity or a stopped writer also trips this |
GUARDAI_OCR_UNAVAILABLE | Tesseract, on /v1/transform/image and /v1/redact/image only. Text and JSON paths are unaffected | Whether the image is the official server image, which bundles Tesseract |
GUARDAI_STREAM_UNPROCESSABLE | A proxy streamed event could not be protected, so the stream ended with a terminal SSE error instead of a clean terminal (counted at the fault site). Not a scanner outage | The upstream streaming dialect: a third-party OpenAI-compatible backend batching multiple choices or tool calls per event (ambiguous_event), or a genuinely new Anthropic event type needing an allowlist bump (unknown_event). The reason label names the cause; route batching backends through the buffered path |
Treat GUARDAI_AUDIT_UNAVAILABLE as outcome-unknown unless the operation's state proves otherwise.
Capacity, queue, writer, and acknowledgement failures before the audit claim leave the operation
uncommitted or roll it back. A failure after the audit claim can leave the operation committed, and a
commit-indeterminate revocation deliberately retains the erasure. For a mutating route that accepts
Idempotency-Key, retry only with the same key. Without that protection, reconcile the domain state
and audit chain before retrying so an already committed operation is not executed twice.
The offline oguardai-server audit verify and audit export commands reject a symlink,
non-regular, broadly readable, or wrongly owned source without modifying it. If an export with
--out fails after creating its destination, it can leave a zero-length mode 0000 residual that
reserves the name. Confirm the residual is the expected regular zero-byte leaf in the trusted
directory, remove that exact leaf, then rerun. If any metadata differs, treat the directory as unsafe
and use a new trusted destination. See
Compliance Controls.
A 422 GUARDAI_OUTPUT_BLOCKED is the opposite case and is not an outage: the content was scanned and
refused, so retrying the same content will be refused again.
OGuardAIHighTransformLatency
Trigger: histogram_quantile(0.95, rate(guardai_transform_duration_seconds_bucket[5m])) > 1.0 for 5 min.
- Check detector sidecar logs:
kubectl logs -l app.kubernetes.io/name=oguardai -c detector --tail=100 - Check CPU pressure:
kubectl top pods -l app.kubernetes.io/name=oguardai - If detector CPU saturated, scale:
kubectl scale deployment/guardai --replicas=4(only if revocation is Redis-backed; see the scaling prerequisite below) - If latency persists, check if NER detector is the bottleneck:
kubectl logs -l app.kubernetes.io/name=oguardai -c detector --tail=50 | grep -i timeout
OGuardAIPromptSecuritySpike
Trigger: rate(guardai_prompt_security_triggers_total[5m]) > 1.0 for 1 min. CRITICAL.
- Review blocked inputs:
kubectl logs -l app.kubernetes.io/name=oguardai -c server --tail=300 | grep 'prompt_security' - Find the source tenant:
kubectl logs -l app.kubernetes.io/name=oguardai -c server --tail=500 \ | grep 'prompt_security' | grep -oP 'tenant=\S+' | sort | uniq -c | sort -rn - If active attack: block source at ingress/WAF level.
- If false positive from new integration: adjust prompt security thresholds.
- Escalate to L3 if pattern is novel or sustained.
OGuardAIOutputGuardSpike
Trigger: rate(guardai_output_guard_triggers_total[5m]) > 0.5 for 2 min.
- Check which entity types are being caught:
kubectl logs -l app.kubernetes.io/name=oguardai -c server --tail=200 | grep 'output_guard' - Review active policy:
kubectl exec deploy/guardai -- cat /app/policies/default/policy.yaml - If LLM is hallucinating PII, check for recent model updates at the provider.
- If new entity type not covered, update policy. See policy-rollback runbook for safe steps.
OGuardAIRateLimitSaturation
Trigger: rate(guardai_rate_limit_rejections_total[5m]) > 10 for 1 min.
- Check which tenants:
kubectl logs -l app.kubernetes.io/name=oguardai -c server --tail=300 | grep 'rate_limit' - If one tenant dominates: contact tenant or raise their limit.
- If all tenants hit limits, scale:
kubectl scale deployment/guardai --replicas=6 kubectl patch hpa guardai -p '{"spec":{"maxReplicas":12}}'
Scaling prerequisite: scale beyond one replica only when the shared Redis backends are configured
(revocation.backend=redis and idempotency.backend=redis). A release installed at one replica with
the default per-pod memory backends is not scale-safe: scaling it lets a value revoked on one pod stay
restorable through another, and lets a retried mutation double-execute on another pod. The Helm chart
refuses to render a multi-replica release without the Redis backends, but a raw kubectl scale of a
single-replica release bypasses that render-time check, so reconfigure first (helm upgrade ... --set revocation.backend=redis --set idempotency.backend=redis --set redis.external.url=...) and let the pods
roll before raising the replica count.
OGuardAINoTraffic
Trigger: rate(guardai_transforms_total[5m]) == 0 for 5 min.
- Health check:
curl -sf http://guardai.internal:3000/livez - Check pod status:
kubectl get pods -l app.kubernetes.io/name=oguardai - Check routing:
kubectl describe ingress guardai && kubectl get endpoints guardai - If healthy but no traffic, verify upstream services have not changed routing.
OGuardAIEntityDetectionSpike
Trigger: rate(guardai_entities_detected_total[5m]) > 100 for 2 min.
- Check if this is legitimate high-volume traffic or a scanning attack.
- If suspicious, escalate to L2.
Escalation Path
| Level | Action | Who |
|---|---|---|
| L1 | Restart pods, check health, follow this runbook | On-call engineer |
| L2 | Root cause analysis, config changes | Senior on-call / team lead |
| L3 | Code-level debugging, hotfix deployment | Engineering team |
Escalate if: issue persists after L1, alert is critical, or root cause unclear after 15 min.