{
  "schema_version": "1.0.0",
  "bot_id": "1.16",
  "bot_name": "ManualOverrideAuditor",
  "slug": "manualoverrideauditor",
  "layer": "Risk",
  "layer_key": "risk",
  "bot_class": "Guardrail",
  "authority": [
    "Veto"
  ],
  "status": "planned",
  "readiness": "Planned",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Risk",
    "bot_class": "Guardrail",
    "authority": "Veto",
    "runs_before": "Override action applied",
    "runs_after": "Override request submitted",
    "applies_to": "Every manual override request for any guardrail \u2014 requires justification, rate-limits overrides, and emits an immutable audit RiskVote for every request",
    "default_mode": "planned",
    "user_visible": "summary-only",
    "developer_owner": "Polytraders core \u2014 Risk pod"
  },
  "purpose": "ManualOverrideAuditor intercepts every request to bypass or adjust a guardrail, enforces a rate limit on overrides per time window, requires a non-empty justification string, and emits an immutable RiskVote audit record for every approved or rejected override attempt. It ensures that manual guardrail bypasses cannot occur silently and that every override is visible to the risk team.",
  "why_it_matters": [
    {
      "failure": "Silent override of a guardrail",
      "consequence": "Without audit enforcement, a guardrail can be bypassed without trace, removing the protective layer without any record for post-hoc review."
    },
    {
      "failure": "Override rate limit bypassed",
      "consequence": "Repeated overrides in a short window can be used to trade in conditions that guardrails are designed to block, effectively disabling the risk controls."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "None \u2014 ManualOverrideAuditor does not read Polymarket APIs",
      "source": "internal",
      "required": false,
      "use": "All inputs come from internal override request payloads."
    }
  ],
  "internal_inputs": [
    {
      "input": "Override request payload (target guardrail, justification, requestor_id)",
      "source": "internal",
      "required": true,
      "use": "Validate justification is non-empty, check rate limit for requestor, and record the override attempt."
    },
    {
      "input": "Override rate limit counter (per requestor per time window)",
      "source": "internal",
      "required": true,
      "use": "Enforce max_overrides_per_window to prevent override abuse."
    },
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "If active, reject all override requests immediately."
    }
  ],
  "raw_params": [
    "max_overrides_per_window \u00b7 int",
    "override_window_minutes \u00b7 int",
    "require_justification \u00b7 bool"
  ],
  "parameters": [
    {
      "name": "max_overrides_per_window",
      "default": 3,
      "warning": 2,
      "hard": 3,
      "controls": "Maximum number of manual override approvals allowed per requestor in the override_window_minutes period.",
      "why_default_matters": "3 overrides per window is enough for legitimate operational needs while preventing systematic abuse.",
      "threshold_logic": [
        {
          "condition": "override_count < 2",
          "action": "APPROVE"
        },
        {
          "condition": "override_count == 2",
          "action": "WARN \u2014 OVERRIDE_AUDITOR_RATE_APPROACHING"
        },
        {
          "condition": "override_count >= 3",
          "action": "REJECT \u2014 OVERRIDE_AUDITOR_RATE_EXCEEDED"
        }
      ],
      "dev_check": "if (overrideCount >= params.max_overrides_per_window) return reject('OVERRIDE_AUDITOR_RATE_EXCEEDED');",
      "user_facing": "You have reached the maximum number of overrides allowed in this time window."
    },
    {
      "name": "override_window_minutes",
      "default": 60,
      "warning": null,
      "hard": null,
      "controls": "Time window in minutes over which the override count is tracked per requestor.",
      "why_default_matters": "A 60-minute window aligns with operational shifts and provides meaningful rate-limiting without being too restrictive for incident response.",
      "threshold_logic": [
        {
          "condition": "always",
          "action": "Count overrides within sliding 60-minute window per requestor"
        }
      ],
      "dev_check": "const windowStart = now_ms() - params.override_window_minutes * 60000;",
      "user_facing": ""
    },
    {
      "name": "require_justification",
      "default": true,
      "warning": null,
      "hard": true,
      "controls": "When true, override requests with empty or missing justification strings are hard-rejected.",
      "why_default_matters": "Requiring justification ensures every override has a documented rationale for post-hoc audit.",
      "threshold_logic": [
        {
          "condition": "require_justification=true AND justification is empty",
          "action": "REJECT \u2014 OVERRIDE_AUDITOR_NO_JUSTIFICATION"
        },
        {
          "condition": "justification is non-empty OR require_justification=false",
          "action": "APPROVE (this check)"
        }
      ],
      "dev_check": "if (params.require_justification && !override.justification.trim()) return reject('OVERRIDE_AUDITOR_NO_JUSTIFICATION');",
      "user_facing": "A justification is required for manual overrides."
    }
  ],
  "default_config": {
    "bot_id": "risk.manual_override_auditor",
    "version": "0.1.0",
    "mode": "hard_guard",
    "defaults": {
      "max_overrides_per_window": 3,
      "override_window_minutes": 60,
      "require_justification": true
    },
    "locked": {
      "require_justification": {
        "immutable": true
      },
      "max_overrides_per_window": {
        "min": 1
      }
    }
  },
  "implementation_flow": [
    "Receive override request payload: target_guardrail, requestor_id, justification, timestamp_ms.",
    "Check KillSwitch; if active, REJECT all overrides immediately.",
    "If require_justification=true and justification is empty, HARD_REJECT(OVERRIDE_AUDITOR_NO_JUSTIFICATION).",
    "Load override count for requestor_id within the last override_window_minutes from Redis.",
    "If override_count >= max_overrides_per_window, HARD_REJECT(OVERRIDE_AUDITOR_RATE_EXCEEDED).",
    "If override_count == max_overrides_per_window - 1, attach WARN(OVERRIDE_AUDITOR_RATE_APPROACHING).",
    "Emit immutable audit RiskVote with requestor_id, target_guardrail, justification, and timestamp.",
    "Increment override counter for requestor_id in Redis (with window_minutes TTL).",
    "Return APPROVE to allow the override to proceed."
  ],
  "decision_logic": {
    "approve": "Justification is non-empty, override count is within the rate limit, and KillSwitch is not active.",
    "reshape_required": "Not used; override requests are binary \u2014 either approved or rejected.",
    "reject": "Justification is missing, rate limit exceeded, or KillSwitch active."
  },
  "decision_output_schema": "RiskVote",
  "decision_output_example": {
    "guard_id": "risk.manual_override_auditor",
    "decision": "HARD_REJECT",
    "severity": "HARD",
    "reason_code": "OVERRIDE_AUDITOR_RATE_EXCEEDED",
    "message": "Requestor ops_user_001 has submitted 3 overrides in the last 60 minutes, exceeding the limit.",
    "constraints": {},
    "inputs_used": [
      "internal.override_counter",
      "internal.killswitch.status"
    ],
    "checked_at": "2026-05-10T15:00:00Z"
  },
  "developer_log": {
    "bot_id": "risk.manual_override_auditor",
    "decision": "HARD_REJECT",
    "reason_code": "OVERRIDE_AUDITOR_RATE_EXCEEDED",
    "inputs_used": [
      "internal.override_counter"
    ],
    "metrics": {
      "requestor_id": "ops_user_001",
      "target_guardrail": "risk.liquidity_guard",
      "override_count_in_window": 3,
      "max_overrides_per_window": 3,
      "window_minutes": 60
    },
    "checked_at": "2026-05-10T15:00:00Z"
  },
  "user_explanations": [
    {
      "situation": "Override blocked \u2014 rate limit",
      "message": "You have submitted too many overrides in the current time window. Please wait before submitting another override request."
    },
    {
      "situation": "Override blocked \u2014 no justification",
      "message": "A justification is required for all manual override requests. Please provide a reason before proceeding."
    },
    {
      "situation": "Override approved \u2014 audit recorded",
      "message": "Your override request has been approved and an audit record has been created. All overrides are logged for review by the risk team."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Failing to record the override audit entry due to a Redis write failure, allowing a silent override.",
    "false_positive_risk": "Rate counter includes expired entries due to a clock skew, causing a false rate-limit rejection.",
    "false_negative_risk": "Concurrent override requests from the same requestor being processed before the counter is incremented, allowing more overrides than the limit.",
    "safe_fallback": "If the Redis override counter is unavailable, HARD_REJECT with OVERRIDE_AUDITOR_DATA_UNAVAILABLE. Never approve when the counter cannot be read or written.",
    "required_dependencies": [
      "Redis override counter store",
      "KillSwitch active flag",
      "Audit log write path"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Approve valid override with justification and within rate limit",
        "setup": "override_count=1, max=3, justification='Incident response'",
        "expected": "APPROVE with audit record emitted"
      },
      {
        "test": "Reject when justification is empty",
        "setup": "justification=''",
        "expected": "HARD_REJECT(OVERRIDE_AUDITOR_NO_JUSTIFICATION)"
      },
      {
        "test": "Reject when rate limit exceeded",
        "setup": "override_count=3, max=3",
        "expected": "HARD_REJECT(OVERRIDE_AUDITOR_RATE_EXCEEDED)"
      },
      {
        "test": "Warn when approaching rate limit",
        "setup": "override_count=2, max=3",
        "expected": "APPROVE with WARN annotation"
      }
    ],
    "integration": [
      {
        "test": "Override counter increments atomically for concurrent requests",
        "expected": "At most max_overrides_per_window overrides approved in any 60-minute window for the same requestor"
      },
      {
        "test": "Audit record immutably stored and retrievable after override",
        "expected": "Audit record queryable from audit log with requestor_id, target_guardrail, and justification"
      }
    ],
    "property": [
      {
        "property": "Empty justification never results in APPROVE when require_justification=true",
        "required": "Always true"
      },
      {
        "property": "Audit record emitted for every APPROVE and HARD_REJECT",
        "required": "Always true \u2014 audit log is the primary compliance artefact"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Require, log, and rate-limit human overrides of any guardrail.",
  "legacy_pm_signals": [
    "Override events: who, when, which guard, REJECT reason",
    "Per-user override frequency vs. budget",
    "Two-person-rule fulfilment for P0 guards"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "risk_compliance"
  ],
  "reason_codes": [
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch active; no overrides allowed.",
      "action": "Immediate HARD_REJECT.",
      "user_message": "Override requests are blocked while trading is paused."
    },
    {
      "code": "OVERRIDE_AUDITOR_NO_JUSTIFICATION",
      "severity": "HARD_REJECT",
      "meaning": "Override request missing required justification string.",
      "action": "HARD_REJECT; do not emit override counter increment.",
      "user_message": "A justification is required for all manual override requests."
    },
    {
      "code": "OVERRIDE_AUDITOR_RATE_EXCEEDED",
      "severity": "HARD_REJECT",
      "meaning": "Requestor has exceeded the max_overrides_per_window limit.",
      "action": "HARD_REJECT; do not emit override counter increment.",
      "user_message": "You have exceeded the override limit for this time window."
    },
    {
      "code": "OVERRIDE_AUDITOR_RATE_APPROACHING",
      "severity": "WARN",
      "meaning": "Override count is one below the hard limit.",
      "action": "Attach WARN annotation; APPROVE.",
      "user_message": ""
    },
    {
      "code": "OVERRIDE_AUDITOR_DATA_UNAVAILABLE",
      "severity": "HARD_REJECT",
      "meaning": "Redis override counter unavailable; cannot enforce rate limit or record audit.",
      "action": "HARD_REJECT (fail-closed).",
      "user_message": "Override system temporarily unavailable. Please try again."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_risk_manualoverrideauditor_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "decision",
          "reason_code",
          "target_guardrail"
        ],
        "meaning": "Total override decisions by type, reason, and target guardrail."
      },
      {
        "name": "polytraders_risk_manualoverrideauditor_overrides_per_window",
        "type": "gauge",
        "unit": "count",
        "labels": [
          "requestor_id"
        ],
        "meaning": "Current override count per requestor within the rolling window."
      },
      {
        "name": "polytraders_risk_manualoverrideauditor_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Latency from request receipt to RiskVote emit."
      }
    ],
    "alerts": [
      {
        "name": "OverrideAuditorRateExceeded",
        "condition": "rate(polytraders_risk_manualoverrideauditor_decisions_total{reason_code='OVERRIDE_AUDITOR_RATE_EXCEEDED'}[5m]) > 0",
        "severity": "P1",
        "runbook": "#runbook-overrideauditor-rate"
      },
      {
        "name": "OverrideAuditorDataUnavailable",
        "condition": "rate(polytraders_risk_manualoverrideauditor_decisions_total{reason_code='OVERRIDE_AUDITOR_DATA_UNAVAILABLE'}[5m]) > 0",
        "severity": "P1",
        "runbook": "#runbook-overrideauditor-data"
      }
    ]
  },
  "state": {
    "store": "redis",
    "shape": "Sorted set per requestor_id recording override timestamps within the window; audit log as an append-only Redis stream.",
    "ttl": "Override counter entries expire at override_window_minutes; audit stream is persistent.",
    "recovery": "Counter rebuilt from audit stream on cold start. If Redis unavailable, HARD_REJECT until restored.",
    "size_estimate": "~50 B per override counter entry; ~200 B per audit log record"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 50,
    "idempotency_key": "override_request_id",
    "timeout_ms": 50,
    "backpressure": "drop newest",
    "locking": "Redis INCR for counter ensures atomic increment; audit stream uses XADD for append-only safety"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "All overrides blocked when kill switch is active.",
        "contract": "HARD_REJECT(KILL_SWITCH_ACTIVE) on every override request when active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "internal.audit_log",
        "why": "Every override request (approved or rejected) emits an immutable audit RiskVote to the audit log.",
        "contract": "Audit record written before APPROVE is returned; write failure triggers HARD_REJECT."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Redis (override counter + audit stream)",
        "endpoint": "internal Redis cluster",
        "sla": "99.99% (in-cluster)",
        "failure_mode": "HARD_REJECT(OVERRIDE_AUDITOR_DATA_UNAVAILABLE) if Redis unavailable."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": false,
    "private_key_access": "none",
    "abuse_vectors": [
      "Rotating requestor_id to bypass per-requestor rate limit",
      "Submitting overrides with boilerplate justification strings to pass the non-empty check without meaningful documentation"
    ],
    "mitigations": [
      "Requestor ID is sourced from authenticated session token; cannot be spoofed by the requester",
      "Justification minimum length and keyword checks planned for v2 of this spec"
    ]
  },
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": false,
    "negrisk_aware": false,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "ManualOverrideAuditor does not interact with CLOB or Polymarket directly; all inputs are internal override request payloads."
  },
  "version": {
    "spec": "2.0.0",
    "implementation": "0.1.0",
    "schema": "2",
    "released": null,
    "planned_release": "Q3-2026"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "n/a",
      "to": "v2-spec",
      "reason": "Spec drafted post-CLOB-V2 cutover; bot not yet implemented",
      "action_taken": "Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain)"
    }
  ],
  "reference_implementation": {
    "pseudocode": "FUNCTION evaluateOverride(request):\n  ks = FETCH internal.killswitch.status\n  IF ks.active:\n    EMIT RiskVote(HARD_REJECT, KILL_SWITCH_ACTIVE); RETURN\n\n  IF params.require_justification AND NOT request.justification.strip():\n    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_NO_JUSTIFICATION); RETURN\n\n  windowStart = now_ms() - params.override_window_minutes * 60000\n  counter = FETCH redis.zcount(request.requestor_id, windowStart, now_ms())\n  IF counter IS NULL:\n    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_DATA_UNAVAILABLE); RETURN\n\n  IF counter >= params.max_overrides_per_window:\n    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_RATE_EXCEEDED); RETURN\n\n  IF counter == params.max_overrides_per_window - 1:\n    annotations.append(WARN(OVERRIDE_AUDITOR_RATE_APPROACHING))\n\n  // Write audit record atomically before approving\n  ok = WRITE redis.xadd('audit_overrides', {\n    requestor_id: request.requestor_id,\n    target_guardrail: request.target_guardrail,\n    justification: request.justification,\n    timestamp_ms: now_ms()\n  })\n  IF NOT ok:\n    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_DATA_UNAVAILABLE); RETURN\n\n  redis.zadd(request.requestor_id, now_ms(), now_ms())\n  EMIT RiskVote(APPROVE, audit_id=ok.entry_id)",
    "sdk_calls": [
      "redis.zcount(requestor_id, windowStart, now)",
      "redis.xadd('audit_overrides', record)",
      "redis.zadd(requestor_id, score, member)",
      "internal.killswitch.status()"
    ],
    "complexity": "O(1) \u2014 constant Redis ops"
  },
  "wire_examples": {
    "input": [
      {
        "label": "Override request \u2014 rate limit exceeded",
        "source": "internal",
        "payload": {
          "override_request_id": "ovr_b8c9d0e1f2a30008",
          "requestor_id": "ops_user_001",
          "target_guardrail": "risk.liquidity_guard",
          "justification": "Incident response \u2014 book feed down",
          "timestamp_ms": 1746800000000
        }
      }
    ],
    "output": [
      {
        "label": "RiskVote \u2014 HARD_REJECT rate exceeded",
        "payload": {
          "guard_id": "risk.manual_override_auditor",
          "decision": "HARD_REJECT",
          "severity": "HARD",
          "reason_code": "OVERRIDE_AUDITOR_RATE_EXCEEDED",
          "message": "ops_user_001 has submitted 3 overrides in the last 60 minutes.",
          "constraints": {},
          "checked_at": "2026-05-10T15:00:00Z"
        }
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "REDIS_UNAVAILABLE",
      "how_to_inject": "Block TCP to Redis cluster",
      "expected_behaviour": "HARD_REJECT(OVERRIDE_AUDITOR_DATA_UNAVAILABLE) on all override requests",
      "recovery": "Returns to normal within one cycle after Redis is restored; counter rebuilt from audit stream."
    },
    {
      "scenario": "RATE_LIMIT_EXCEEDED",
      "how_to_inject": "Submit 3 overrides in 60 minutes for the same requestor_id",
      "expected_behaviour": "First 3 APPROVE; 4th HARD_REJECT(OVERRIDE_AUDITOR_RATE_EXCEEDED)",
      "recovery": "Returns to APPROVE after the rolling window expires."
    },
    {
      "scenario": "EMPTY_JUSTIFICATION",
      "how_to_inject": "Submit override request with justification=''",
      "expected_behaviour": "HARD_REJECT(OVERRIDE_AUDITOR_NO_JUSTIFICATION)",
      "recovery": "Immediate on resubmission with non-empty justification."
    }
  ],
  "runbook": {
    "summary": "ManualOverrideAuditor incidents typically involve a Redis failure causing fail-closed rejections, or a legitimate rate-limit breach requiring escalation to risk pod lead for review.",
    "oncall_actions": [
      {
        "alert": "OverrideAuditorRateExceeded",
        "first_step": "Identify requestor_id from the reason_code log; review override audit stream to determine if requests are legitimate incident response or potential abuse.",
        "escalation": "Risk pod lead immediately; review override audit records.",
        "diagnosis": "",
        "mitigation": ""
      },
      {
        "alert": "OverrideAuditorDataUnavailable",
        "first_step": "Check Redis cluster connectivity; confirm audit stream is writable.",
        "escalation": "Infra on-call if Redis unavailable > 1 minute.",
        "diagnosis": "",
        "mitigation": ""
      }
    ],
    "manual_overrides": [
      {
        "command": "polytraders risk reset-override-counter --requestor-id <id>",
        "effect": "After a confirmed legitimate incident response where the rate limit was exceeded; requires risk pod lead written approval."
      }
    ],
    "healthcheck": "GET /internal/health/manualoverrideauditor \u2192 green: Redis reachable, audit stream writable, override counter TTL active; red: Redis unreachable, audit stream write failure, or counter TTL expired"
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass for rate limit, justification, and Redis failure scenarios",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "Audit records verified immutable in Redis stream over 48h shadow run",
        "how_measured": "Audit log audit by risk team",
        "threshold": "100% records present and unmodified"
      }
    ],
    "to_general_live": [
      {
        "gate": "Zero DATA_UNAVAILABLE rejections during normal hours over 7 days",
        "how_measured": "OverrideAuditorDataUnavailable alert history",
        "threshold": "0 firings"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "RiskVote"
    ],
    "topics": [
      "polytraders.reports.risk"
    ],
    "partition_by": "trace_id",
    "cadence": "every-event",
    "retention_class": "2y",
    "sampling_rule": "emit-every",
    "bus_failure_action": "fail-closed",
    "user_visible": "summary-only",
    "consumes_kinds": [
      "ObservationReport"
    ]
  },
  "capital_impact": "Direct",
  "mode_support": [
    "quarantine"
  ],
  "v3_status": {
    "phase": 4,
    "phase_name": "Core risk",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}