{
  "schema_version": "1.0.0",
  "bot_id": "1.5",
  "bot_name": "RateLimitGovernor",
  "slug": "ratelimitgovernor",
  "layer": "Risk",
  "layer_key": "risk",
  "bot_class": "Guardrail",
  "authority": [
    "Reject",
    "Reshape"
  ],
  "status": "live",
  "readiness": "General live",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Risk",
    "bot_class": "Guardrail",
    "authority": "Reject, Reshape",
    "runs_before": "ExecutionPlan emit",
    "runs_after": "Strategy OrderIntent",
    "applies_to": "Every OrderIntent \u2014 checks per-market and per-account order send-rate against Polymarket CLOB limits before allowing execution",
    "default_mode": "general_live",
    "user_visible": "no",
    "developer_owner": "Polytraders core \u2014 Risk pod"
  },
  "purpose": "RateLimitGovernor prevents the system from exceeding Polymarket's CLOB order send-rate limits at both the per-market and per-account levels. It reads live rate-limit headers from CLOB responses (X-RateLimit-Remaining, X-RateLimit-Reset), maintains sliding-window counters, and either delays (reshapes) or rejects OrderIntents when the remaining budget falls below configured thresholds. Cancel and risk-flatten requests always receive priority allocation over new open orders. The bot is fail-closed: if rate-limit state cannot be determined, new order intents are rejected until state is re-established.",
  "why_it_matters": [
    {
      "failure": "Order send-rate exceeds CLOB limit",
      "consequence": "Polymarket's CLOB enforces rate limits via Cloudflare. Exceeding the limit produces HTTP 429 responses, which cause order rejections, increased latency, and potential temporary IP/key bans that block all trading \u2014 including urgent risk-flatten operations."
    },
    {
      "failure": "No priority for cancel/risk-flatten requests",
      "consequence": "If rate budget is exhausted by new open orders, urgent cancellation or position-flatten requests may be queued or dropped, leaving dangerous open exposure during fast market moves."
    },
    {
      "failure": "Rate budget not read from live CLOB headers",
      "consequence": "Hard-coded rate limits may diverge from Polymarket's actual published limits. Reading live headers ensures the system adapts to any policy changes without a code deploy."
    },
    {
      "failure": "Per-market throttle not separated from per-account throttle",
      "consequence": "A high-frequency strategy on one market can exhaust the account's global rate budget, silently starving other markets of order capacity."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "CLOB rate-limit response headers: X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Limit",
      "source": "clob_auth",
      "required": true,
      "use": "Track the live remaining request budget and reset time per sliding window. Update internal counters after every CLOB response."
    }
  ],
  "internal_inputs": [
    {
      "input": "Per-market sliding-window order counter",
      "source": "internal",
      "required": true,
      "use": "Count the number of order intents sent to each market within the current rate-limit window."
    },
    {
      "input": "Per-account global order counter",
      "source": "internal",
      "required": true,
      "use": "Count total order intents across all markets to enforce the account-level rate cap."
    },
    {
      "input": "Intent classification: open / cancel / risk-flatten",
      "source": "internal",
      "required": true,
      "use": "Cancel and risk-flatten intents always receive priority allocation from the reserved cancel budget; they bypass the standard rate check."
    },
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "If KillSwitch is active, reject all new open orders immediately. Cancel/flatten requests still use the priority path."
    }
  ],
  "raw_params": [
    "public_req_per_min \u00b7 int",
    "trading_req_per_min \u00b7 int",
    "priority_cancel_over_open \u00b7 bool",
    "priority_risk_flatten \u00b7 bool"
  ],
  "parameters": [
    {
      "name": "public_req_per_min",
      "default": 200,
      "warning": 160,
      "hard": 200,
      "controls": "Maximum number of public (unauthenticated) CLOB requests per minute across all strategies.",
      "why_default_matters": "Polymarket's published public API limit is ~200 requests/min. Staying at or below this prevents 429 responses on market-data reads that many strategies depend on.",
      "threshold_logic": [
        {
          "condition": "requests \u2264 160 / min",
          "action": "APPROVE"
        },
        {
          "condition": "160\u2013200 / min",
          "action": "WARN \u2014 log rate pressure; begin shedding lowest-priority requests"
        },
        {
          "condition": "> 200 / min",
          "action": "REJECT \u2014 RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED"
        }
      ],
      "dev_check": "if (windowCount.public >= p.hard) return reject('RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED'); else if (windowCount.public >= p.warning) return warn('RATE_LIMIT_GOVERNOR_BUDGET_WARN');",
      "user_facing": ""
    },
    {
      "name": "trading_req_per_min",
      "default": 100,
      "warning": 80,
      "hard": 100,
      "controls": "Maximum number of authenticated order-placement requests per minute across all strategies and markets.",
      "why_default_matters": "Polymarket's published trading API limit is ~100 order operations/min per account. Exceeding this risks 429 and potential key suspension.",
      "threshold_logic": [
        {
          "condition": "requests \u2264 80 / min",
          "action": "APPROVE"
        },
        {
          "condition": "80\u2013100 / min",
          "action": "RESHAPE \u2014 defer non-priority intents to next window"
        },
        {
          "condition": "> 100 / min",
          "action": "REJECT \u2014 RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED"
        }
      ],
      "dev_check": "if (windowCount.trading >= p.hard) return reject('RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED'); else if (windowCount.trading >= p.warning) return reshape({defer_ms: msUntilWindowReset()});",
      "user_facing": ""
    },
    {
      "name": "priority_cancel_over_open",
      "default": true,
      "warning": null,
      "hard": true,
      "controls": "When true, cancel requests are always processed before new open orders and draw from a reserved cancel budget that cannot be consumed by open orders.",
      "why_default_matters": "Cancel operations are risk-reducing. Prioritising them over open orders ensures the system can always cancel stale quotes during fast market moves, even if the rate budget is nearly full.",
      "threshold_logic": [
        {
          "condition": "priority_cancel_over_open = true AND intent is cancel",
          "action": "Always APPROVE from reserved cancel budget, bypassing standard rate check"
        },
        {
          "condition": "priority_cancel_over_open = false",
          "action": "Cancel requests compete with open orders for the shared rate budget"
        }
      ],
      "dev_check": "if (params.priority_cancel_over_open && intent.type == 'CANCEL') return approve_from_reserved_budget();",
      "user_facing": ""
    },
    {
      "name": "priority_risk_flatten",
      "default": true,
      "warning": null,
      "hard": true,
      "controls": "When true, risk-flatten (emergency close-all) requests bypass rate checks entirely and are sent immediately regardless of remaining budget.",
      "why_default_matters": "Risk-flatten operations must never be delayed by a rate-limit check. Allowing them to bypass ensures the system can always respond to a KillSwitch or drawdown circuit-breaker event.",
      "threshold_logic": [
        {
          "condition": "priority_risk_flatten = true AND intent is risk-flatten",
          "action": "Immediately APPROVE \u2014 bypasses all rate counters"
        },
        {
          "condition": "priority_risk_flatten = false",
          "action": "Risk-flatten intents compete with standard budget (not recommended)"
        }
      ],
      "dev_check": "if (params.priority_risk_flatten && intent.type == 'RISK_FLATTEN') return approve_unconditionally();",
      "user_facing": ""
    }
  ],
  "default_config": {
    "bot_id": "risk.rate_limit_governor",
    "version": "2.0.0",
    "mode": "hard_guard",
    "defaults": {
      "public_req_per_min": 200,
      "trading_req_per_min": 100,
      "priority_cancel_over_open": true,
      "priority_risk_flatten": true
    },
    "locked": {
      "priority_risk_flatten": {
        "min": true
      },
      "trading_req_per_min": {
        "max": 100
      }
    }
  },
  "implementation_flow": [
    "Receive OrderIntent from Strategy layer with intent type (open / cancel / risk-flatten).",
    "Check KillSwitch active flag; if active, reject all open-order intents immediately. Cancel and risk-flatten intents still proceed via their priority paths.",
    "If intent type is RISK_FLATTEN and priority_risk_flatten=true, approve unconditionally and bypass all rate counters.",
    "If intent type is CANCEL and priority_cancel_over_open=true, approve from the reserved cancel budget and bypass the standard trading counter.",
    "Read the current sliding-window counter for the target market (per-market bucket) and for the account (global trading bucket).",
    "Check account-level trading counter against trading_req_per_min. If at or above the hard limit, return HARD_REJECT with RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED.",
    "Check per-market counter against the per-market sub-limit (trading_req_per_min / active_market_count). If at or above the per-market hard limit, return HARD_REJECT with RATE_LIMIT_GOVERNOR_MARKET_THROTTLED.",
    "If either counter is in the warning zone (80\u2013100% of limit), return RESHAPE with constraints.defer_ms set to the milliseconds until the current window resets (from X-RateLimit-Reset header).",
    "Approve the intent; increment both the per-market and per-account counters; update counters from the next CLOB response headers.",
    "On each CLOB response, sync internal counters with X-RateLimit-Remaining to detect drift between internal estimates and Polymarket-reported values."
  ],
  "decision_logic": {
    "approve": "Both per-market and per-account trading counters are below the warning threshold for the current sliding window, and the intent is not a cancel or risk-flatten (which have their own paths).",
    "reshape_required": "Either the per-market or per-account counter is in the warning zone (80\u2013100% of limit) \u2014 emit constraints.defer_ms indicating how long to wait before re-submitting.",
    "reject": "Account-level or per-market counter has reached the hard limit for the current window; KillSwitch is active (for open orders); or rate-limit state is unavailable (fail-closed).",
    "warning_only": "Not used \u2014 RateLimitGovernor has reject authority. Rate pressure is either deferred (RESHAPE) or hard-rejected when the budget is exhausted."
  },
  "decision_output_schema": "RiskVote",
  "decision_output_example": {
    "guard_id": "risk.rate_limit_governor",
    "decision": "RESHAPE_REQUIRED",
    "severity": "WARN",
    "reason_code": "RATE_LIMIT_GOVERNOR_BUDGET_WARN",
    "message": "Trading rate at 87/100 req/min (87%). Deferring intent by 4200ms until window resets.",
    "constraints": {
      "defer_ms": 4200,
      "passive_only": false,
      "close_only": false
    },
    "inputs_used": [
      "clob_auth.ratelimit_headers",
      "internal.sliding_window.trading",
      "internal.killswitch.status"
    ],
    "checked_at": "2026-05-09T12:01:00Z"
  },
  "developer_log": {
    "bot_id": "risk.rate_limit_governor",
    "decision": "RESHAPE_REQUIRED",
    "reason_code": "RATE_LIMIT_GOVERNOR_BUDGET_WARN",
    "inputs_used": [
      "clob_auth.ratelimit_headers",
      "internal.sliding_window.trading"
    ],
    "metrics": {
      "trading_counter": 87,
      "trading_hard_limit": 100,
      "market_counter": 22,
      "market_hard_limit": 25,
      "window_reset_in_ms": 4200,
      "last_ratelimit_remaining_from_header": 13
    },
    "checked_at": "2026-05-09T12:01:00Z"
  },
  "user_explanations": [
    {
      "situation": "Order deferred \u2014 rate limit approaching",
      "message": "Your order has been briefly delayed to stay within trading rate limits. It will be submitted automatically in a few seconds."
    },
    {
      "situation": "Order blocked \u2014 rate limit exhausted",
      "message": "The maximum number of orders for this minute has been reached. Your order will be requeued for the next available window."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Approving too many intents because the internal sliding-window counter drifts from Polymarket's actual rate-limit state, leading to unexpected 429 responses that block all order operations including urgent cancellations.",
    "false_positive_risk": "Deferring or rejecting orders that could have been sent because the internal counter overestimates usage (e.g. after a burst that was partially absorbed by burst headroom on the CLOB side).",
    "false_negative_risk": "Approving too many intents during the brief window between a rate-limit reset and the next X-RateLimit-Remaining header being read, slightly exceeding the window budget.",
    "safe_fallback": "If the rate-limit header cannot be read (network error, malformed response) or the internal counter is in an unknown state, RateLimitGovernor hard-rejects all non-priority new open orders until the counter is re-synchronised from a successful CLOB response. Cancel and risk-flatten intents are never blocked.",
    "required_dependencies": [
      "CLOB Auth API (X-RateLimit headers on responses)",
      "Internal sliding-window counter store",
      "KillSwitch active flag"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Approve when both counters are below warning threshold",
        "setup": "trading_counter=50, trading_hard=100, market_counter=10, market_hard=25",
        "expected": "APPROVE with no constraints"
      },
      {
        "test": "Reshape with defer_ms when trading counter is in warning zone",
        "setup": "trading_counter=85, trading_hard=100, window_reset_in_ms=5000",
        "expected": "RESHAPE with constraints.defer_ms=5000"
      },
      {
        "test": "Hard-reject when trading counter reaches hard limit",
        "setup": "trading_counter=100, trading_hard=100",
        "expected": "HARD_REJECT with reason_code=RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED"
      },
      {
        "test": "Hard-reject when per-market counter reaches per-market limit",
        "setup": "market_counter=25, market_hard=25",
        "expected": "HARD_REJECT with reason_code=RATE_LIMIT_GOVERNOR_MARKET_THROTTLED"
      },
      {
        "test": "Cancel intent bypasses standard rate check via reserved budget",
        "setup": "trading_counter=100, intent.type=CANCEL, priority_cancel_over_open=true",
        "expected": "APPROVE from reserved cancel budget, regardless of trading_counter"
      },
      {
        "test": "Risk-flatten bypasses all rate checks",
        "setup": "trading_counter=100, intent.type=RISK_FLATTEN, priority_risk_flatten=true",
        "expected": "APPROVE unconditionally"
      },
      {
        "test": "Fail-closed when rate-limit state unknown",
        "setup": "CLOB API returns no X-RateLimit headers and internal counter is uninitialised",
        "expected": "HARD_REJECT(RATE_LIMIT_GOVERNOR_STATE_UNKNOWN) for all non-priority intents"
      }
    ],
    "integration": [
      {
        "test": "Internal counter syncs with live CLOB X-RateLimit-Remaining header",
        "expected": "After a burst of 80 orders, internal counter reflects the value in X-RateLimit-Remaining within one response cycle"
      },
      {
        "test": "Cancel always passes when trading budget is exhausted",
        "expected": "APPROVE for CANCEL intent when trading_counter == trading_req_per_min and priority_cancel_over_open=true"
      },
      {
        "test": "KillSwitch active rejects open orders but allows cancels",
        "expected": "HARD_REJECT(KILL_SWITCH_ACTIVE) for OPEN intents; APPROVE for CANCEL intents when KillSwitch is active"
      }
    ],
    "property": [
      {
        "property": "Risk-flatten intents are never rejected or deferred by rate checks",
        "required": "Always true when priority_risk_flatten=true"
      },
      {
        "property": "Trading counter never exceeds hard limit",
        "required": "Always true \u2014 counter is checked before incrementing; only incremented on APPROVE"
      },
      {
        "property": "Rate-limit state unknown never results in APPROVE for open orders",
        "required": "Always true \u2014 RATE_LIMIT_GOVERNOR_STATE_UNKNOWN produces HARD_REJECT for non-priority intents"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Stay inside Polymarket's published API limits across all strategies.",
  "legacy_pm_signals": [
    "Polymarket-published request and order rate budgets, configured at runtime \u2014 never hardcoded",
    "Cloudflare-enforced sliding-window counters per endpoint",
    "429 responses observed (priority cancel / risk-flatten always preserved)"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "risk_compliance"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "clob_auth",
    "internal"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "2.1.0",
    "schema": "2",
    "released": "2026-04-28"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "v1",
      "to": "v2",
      "reason": "CLOB V2 cutover",
      "action_taken": "Switched to py-clob-client-v2; updated rate-limit header parsing to the V2 CLOB response schema. Removed feeRateBps from order-size estimation used in budget calculations. Rate counters now track by intent_id (V2 idempotency key) rather than nonce. EIP-712 domain version updated to '2' in outbound request accounting."
    }
  ],
  "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": "RateLimitGovernor reads live X-RateLimit-* headers from the Polymarket CLOB V2 API to keep its internal sliding-window counters accurate. The bot does not inspect order fields for builder codes or negRisk flags; it operates purely on request-rate metadata."
  },
  "reference_implementation": {
    "summary": "Maintains per-market and per-account sliding-window counters. On each intent, checks the KillSwitch and priority paths first, then evaluates both counters before approving, reshaping, or rejecting. Syncs counters from live X-RateLimit headers on every CLOB response.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION evaluateRateLimit(intent):\n  // --- 0. KillSwitch gate ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active AND intent.type == 'OPEN':\n    EMIT RiskVote(decision=HARD_REJECT, reason=KILL_SWITCH_ACTIVE)\n    RETURN\n\n  // --- 1. Priority paths ---\n  IF intent.type == 'RISK_FLATTEN' AND params.priority_risk_flatten:\n    EMIT RiskVote(decision=APPROVE, reason=RATE_LIMIT_GOVERNOR_PRIORITY_FLATTEN)\n    RETURN\n  IF intent.type == 'CANCEL' AND params.priority_cancel_over_open:\n    reserved = FETCH internal.counter.reserved_cancel_budget\n    IF reserved.remaining > 0:\n      reserved.remaining -= 1\n      EMIT RiskVote(decision=APPROVE, reason=RATE_LIMIT_GOVERNOR_PRIORITY_CANCEL)\n    ELSE:\n      EMIT RiskVote(decision=HARD_REJECT, reason=RATE_LIMIT_GOVERNOR_CANCEL_BUDGET_EXHAUSTED)\n    RETURN\n\n  // --- 2. Read counters ---\n  tradingWindow = FETCH internal.counter.trading_window\n  marketWindow  = FETCH internal.counter.market_window(intent.market_id)\n  IF tradingWindow IS NULL OR marketWindow IS NULL:\n    EMIT RiskVote(decision=HARD_REJECT, reason=RATE_LIMIT_GOVERNOR_STATE_UNKNOWN)\n    RETURN\n\n  // --- 3. Hard limit checks ---\n  IF tradingWindow.count >= params.trading_req_per_min.hard:\n    EMIT RiskVote(decision=HARD_REJECT, reason=RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED)\n    RETURN\n  perMarketHard = params.trading_req_per_min.hard / activeMarketCount()\n  IF marketWindow.count >= perMarketHard:\n    EMIT RiskVote(decision=HARD_REJECT, reason=RATE_LIMIT_GOVERNOR_MARKET_THROTTLED)\n    RETURN\n\n  // --- 4. Warning zone \u2192 reshape with defer ---\n  IF tradingWindow.count >= params.trading_req_per_min.warning OR\n     marketWindow.count >= perMarketHard * 0.8:\n    resetMs = tradingWindow.reset_at_ms - now_ms()\n    EMIT RiskVote(decision=RESHAPE_REQUIRED,\n                  reason=RATE_LIMIT_GOVERNOR_BUDGET_WARN,\n                  constraints={defer_ms: max(resetMs, 0)})\n    RETURN\n\n  // --- 5. Approve and increment counters ---\n  tradingWindow.count += 1\n  marketWindow.count  += 1\n  EMIT RiskVote(decision=APPROVE,\n                reason=RATE_LIMIT_GOVERNOR_PASS,\n                checked_at=now_iso())\n\n// Called on every CLOB API response to sync internal counters\nFUNCTION syncFromClobHeaders(response):\n  remaining = response.headers.get('X-RateLimit-Remaining')\n  reset_at  = response.headers.get('X-RateLimit-Reset')\n  IF remaining IS NOT NULL:\n    tradingWindow.count = params.trading_req_per_min.hard - int(remaining)\n    tradingWindow.reset_at_ms = int(reset_at) * 1000\n",
    "sdk_calls": [
      "clob_auth.GET('/orders') \u2014 reads X-RateLimit-Remaining header from response",
      "clob_auth.POST('/order') \u2014 reads X-RateLimit-Remaining header from response",
      "internal.counter.trading_window.get()",
      "internal.counter.market_window.get(market_id)",
      "internal.killswitch.status()"
    ],
    "complexity": "O(1) \u2014 constant-time counter lookup and increment per intent"
  },
  "wire_examples": {
    "input": [
      {
        "label": "OrderIntent \u2014 open order when rate is in warning zone",
        "source": "internal",
        "payload": {
          "intent_id": "int_e5f6a7b8c9d0e1f2",
          "market_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
          "side": "BUY",
          "outcome": "YES",
          "size_usd": 200,
          "price": 0.48,
          "intent_type": "OPEN",
          "generated_at_ms": 1746787260000
        }
      },
      {
        "label": "Internal rate counter state",
        "source": "internal",
        "payload": {
          "trading_window_count": 87,
          "trading_hard_limit": 100,
          "market_window_count": 22,
          "market_hard_limit": 25,
          "window_reset_at_ms": 1746787320000,
          "last_header_remaining": 13
        }
      }
    ],
    "output": [
      {
        "label": "RiskVote \u2014 RESHAPE (defer until window reset)",
        "payload": {
          "guard_id": "risk.rate_limit_governor",
          "decision": "RESHAPE_REQUIRED",
          "severity": "WARN",
          "reason_code": "RATE_LIMIT_GOVERNOR_BUDGET_WARN",
          "message": "Trading rate 87/100 req/min (87%). Defer by 4200ms until window resets at 2026-05-09T12:02:00Z.",
          "constraints": {
            "defer_ms": 4200,
            "passive_only": false,
            "close_only": false
          },
          "inputs_used": [
            "clob_auth.ratelimit_headers",
            "internal.sliding_window.trading",
            "internal.sliding_window.market"
          ],
          "checked_at": "2026-05-09T12:01:15Z"
        }
      },
      {
        "label": "RiskVote \u2014 HARD_REJECT (budget exhausted)",
        "payload": {
          "guard_id": "risk.rate_limit_governor",
          "decision": "HARD_REJECT",
          "severity": "HARD",
          "reason_code": "RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED",
          "message": "Trading rate 100/100 req/min. Rate budget exhausted for this window. Intent dropped.",
          "constraints": {},
          "inputs_used": [
            "clob_auth.ratelimit_headers",
            "internal.sliding_window.trading"
          ],
          "checked_at": "2026-05-09T12:01:30Z"
        }
      }
    ]
  },
  "reason_codes": [
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch is active; all open-order intents are rejected. Cancel and risk-flatten intents are unaffected.",
      "action": "Return HARD_REJECT for OPEN intents; CANCEL and RISK_FLATTEN intents proceed via priority path.",
      "user_message": "Trading is currently paused. Please try again later."
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED",
      "severity": "HARD_REJECT",
      "meaning": "Account-level trading counter has reached the hard limit for the current sliding window.",
      "action": "Return HARD_REJECT; log window reset time. Intent may be requeued after window reset.",
      "user_message": ""
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_MARKET_THROTTLED",
      "severity": "HARD_REJECT",
      "meaning": "Per-market order counter has reached the per-market sub-limit for the current window.",
      "action": "Return HARD_REJECT for this market; intents on other markets are unaffected.",
      "user_message": ""
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_BUDGET_WARN",
      "severity": "RESHAPE",
      "meaning": "Trading counter is in the warning zone (80\u2013100% of limit). Order deferred until window reset.",
      "action": "Return RESHAPE_REQUIRED with constraints.defer_ms = ms until window reset.",
      "user_message": "Your order has been briefly delayed to stay within trading rate limits."
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_STATE_UNKNOWN",
      "severity": "HARD_REJECT",
      "meaning": "Internal rate-limit state is uninitialised or cannot be read; fail-closed to prevent accidental over-sending.",
      "action": "Return HARD_REJECT for all non-priority intents until state is re-established from CLOB headers.",
      "user_message": ""
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_PRIORITY_CANCEL",
      "severity": "INFO",
      "meaning": "Cancel intent approved from reserved cancel budget, bypassing the standard trading counter.",
      "action": "Decrement reserved cancel budget; emit APPROVE.",
      "user_message": ""
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_PRIORITY_FLATTEN",
      "severity": "INFO",
      "meaning": "Risk-flatten intent approved unconditionally, bypassing all rate counters.",
      "action": "Emit APPROVE without touching any counter.",
      "user_message": ""
    },
    {
      "code": "RATE_LIMIT_GOVERNOR_PASS",
      "severity": "INFO",
      "meaning": "Both per-market and per-account counters are within limits. Intent approved.",
      "action": "Increment counters; emit APPROVE.",
      "user_message": ""
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_risk_ratelimitgovernor_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "decision",
          "reason_code"
        ],
        "meaning": "Total RiskVote decisions emitted by RateLimitGovernor, broken down by decision type and reason code."
      },
      {
        "name": "polytraders_risk_ratelimitgovernor_trading_window_utilisation",
        "type": "gauge",
        "unit": "ratio",
        "labels": [],
        "meaning": "Current trading window counter as a fraction of trading_req_per_min.hard. Alerts at 0.8."
      },
      {
        "name": "polytraders_risk_ratelimitgovernor_market_window_utilisation",
        "type": "gauge",
        "unit": "ratio",
        "labels": [
          "market_id"
        ],
        "meaning": "Per-market window counter as a fraction of the per-market sub-limit."
      },
      {
        "name": "polytraders_risk_ratelimitgovernor_clob_header_sync_age_seconds",
        "type": "gauge",
        "unit": "seconds",
        "labels": [],
        "meaning": "Seconds since the last successful X-RateLimit-Remaining header was read from a CLOB response. Alerts if stale."
      },
      {
        "name": "polytraders_risk_ratelimitgovernor_429_responses_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "endpoint"
        ],
        "meaning": "Number of HTTP 429 responses received from the CLOB, indicating the governor failed to prevent an over-send."
      },
      {
        "name": "polytraders_risk_ratelimitgovernor_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Wall-clock time from OrderIntent receipt to RiskVote emit. P99 target < 5ms (in-memory counter lookup)."
      }
    ],
    "alerts": [
      {
        "name": "RateLimitGovernorBudgetCritical",
        "condition": "polytraders_risk_ratelimitgovernor_trading_window_utilisation > 0.9",
        "severity": "page",
        "runbook": "#runbook-ratelimitgovernor-budget-critical"
      },
      {
        "name": "RateLimitGovernor429Observed",
        "condition": "rate(polytraders_risk_ratelimitgovernor_429_responses_total[5m]) > 0",
        "severity": "page",
        "runbook": "#runbook-ratelimitgovernor-429"
      },
      {
        "name": "RateLimitGovernorHeaderSyncStale",
        "condition": "polytraders_risk_ratelimitgovernor_clob_header_sync_age_seconds > 60",
        "severity": "warn",
        "runbook": "#runbook-ratelimitgovernor-header-sync"
      },
      {
        "name": "RateLimitGovernorHighRejectRate",
        "condition": "rate(polytraders_risk_ratelimitgovernor_decisions_total{decision='HARD_REJECT'}[5m]) / rate(polytraders_risk_ratelimitgovernor_decisions_total[5m]) > 0.3",
        "severity": "warn",
        "runbook": "#runbook-ratelimitgovernor-reject-rate"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Risk overview / RateLimitGovernor",
      "Grafana \u2014 API health / CLOB rate-limit utilisation and 429 history"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "in-memory",
    "shape": "Two sliding-window counter objects: (1) global trading counter {count: int, reset_at_ms: int}; (2) per-market counter map {market_id \u2192 {count: int, reset_at_ms: int}}. Also a reserved cancel budget counter {remaining: int}. All updated on every intent evaluation and synced from CLOB response headers.",
    "ttl": "Counter windows expire at reset_at_ms (typically 60s sliding windows). On expiry, counters reset to 0.",
    "recovery": "On cold start, all counters initialise to 0. The first CLOB response syncs them to the live X-RateLimit-Remaining value. Until the first sync, intents are allowed up to 50% of the hard limit as a conservative bootstrap budget.",
    "size_estimate": "< 10 KB for all active market counters; typically < 50 active markets"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 1000,
    "idempotency_key": "intent_id",
    "timeout_ms": 5,
    "backpressure": "drop newest",
    "locking": "global mutex on counter increment to prevent double-counting under concurrent intent bursts"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "Global brake \u2014 checked first; KillSwitch causes rejection of all open-order intents but does not block cancel/flatten.",
        "contract": "RiskVote.HARD_REJECT(KILL_SWITCH_ACTIVE) for OPEN intents only."
      }
    ],
    "emits_to": [
      {
        "bot_id": "exec.smart_router",
        "why": "Approved RiskVotes pass to SmartRouter for ExecutionPlan construction. RESHAPE with defer_ms instructs SmartRouter to wait before submitting.",
        "contract": "constraints.defer_ms is binding for SmartRouter scheduling."
      }
    ],
    "sibling": [
      {
        "bot_id": "risk.portfolio_guard",
        "why": "Sibling guardrail; both must pass before SmartRouter runs."
      },
      {
        "bot_id": "risk.liquidity_guard",
        "why": "Sibling guardrail."
      }
    ],
    "external": [
      {
        "service": "Polymarket CLOB V2 (rate-limit headers)",
        "endpoint": "https://clob.polymarket.com",
        "sla": "99.95% / 200ms p99 (Polymarket-published)",
        "fallback": "If CLOB is unreachable and no headers have been read for > 60s, HARD_REJECT(RATE_LIMIT_GOVERNOR_STATE_UNKNOWN) for all non-priority intents."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": false,
    "private_key_access": "none",
    "abuse_vectors": [
      "Strategy flooding small-sized intents to consume the rate budget of other strategies",
      "Race condition: two concurrent strategies both checking the counter before either increments, causing double-approval near the hard limit"
    ],
    "mitigations": [
      "Global mutex on counter increment prevents concurrent double-counting",
      "Per-market sub-limits prevent a single market from consuming the full account budget",
      "Cancel and risk-flatten budgets are reserved and cannot be consumed by open-order intents"
    ],
    "contract_calls": []
  },
  "failure_injection": [
    {
      "scenario": "BUDGET_EXHAUSTED",
      "how_to_inject": "Set internal trading_window.count = 100 (equal to hard limit)",
      "expected_behaviour": "All subsequent OPEN intents return HARD_REJECT(RATE_LIMIT_GOVERNOR_BUDGET_EXHAUSTED); CANCEL and RISK_FLATTEN intents are unaffected",
      "recovery": "Returns to APPROVE once the window resets (reset_at_ms passes) and counter is set back to 0."
    },
    {
      "scenario": "429_FROM_CLOB",
      "how_to_inject": "Return HTTP 429 from CLOB mock for POST /order",
      "expected_behaviour": "RateLimitGovernor429Observed alert fires; bot immediately sets trading_window.count to hard limit and rejects all open intents until window resets",
      "recovery": "Returns to normal on next window reset after no further 429 responses."
    },
    {
      "scenario": "HEADER_SYNC_STALE",
      "how_to_inject": "Stop returning X-RateLimit-Remaining header in CLOB mock responses for 70s",
      "expected_behaviour": "RateLimitGovernorHeaderSyncStale alert fires; bot conservatively reduces allowed rate to 50% of hard limit",
      "recovery": "Returns to full limit once headers are visible in responses again."
    },
    {
      "scenario": "STATE_UNKNOWN_COLD_START",
      "how_to_inject": "Restart bot with no pre-loaded counter state",
      "expected_behaviour": "Intents allowed up to 50% of hard limit as bootstrap budget; first CLOB response syncs counter to live value",
      "recovery": "Full rate budget available within one CLOB response cycle."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set internal.killswitch.status.active = true",
      "expected_behaviour": "HARD_REJECT(KILL_SWITCH_ACTIVE) for all OPEN intents; CANCEL intents still APPROVE from reserved budget",
      "recovery": "Returns to normal on manual KillSwitch reset."
    }
  ],
  "runbook": {
    "summary": "RateLimitGovernor incidents are typically a 429 from Polymarket (counter miscalibrated or burst overrun) or a stale header sync. A 429 in production is a P1 incident because it may block all order operations including risk-flattening.",
    "oncall_actions": [
      {
        "alert": "RateLimitGovernor429Observed",
        "first_action": "Check which endpoint triggered the 429 (label on 429 counter). Immediately verify that cancel and risk-flatten operations are still working via the priority path.",
        "escalate_to": "Risk pod lead immediately; 429 is a P1 incident."
      },
      {
        "alert": "RateLimitGovernorBudgetCritical",
        "first_action": "Identify which strategies are consuming the most order budget from the market utilisation gauge. Consider pausing low-priority strategies.",
        "escalate_to": "Risk pod lead if utilisation reaches 100%."
      },
      {
        "alert": "RateLimitGovernorHeaderSyncStale",
        "first_action": "Check CLOB API connectivity. If CLOB is reachable but not returning rate-limit headers, the bot will conservatively limit to 50% of budget.",
        "escalate_to": "Infra on-call if CLOB API is returning malformed headers."
      },
      {
        "alert": "RateLimitGovernorHighRejectRate",
        "first_action": "Check reason_code distribution. If dominated by BUDGET_EXHAUSTED, reduce strategy order frequency. If dominated by MARKET_THROTTLED, check if a single market is over-active.",
        "escalate_to": "Risk pod lead if reject rate is causing missed fills on time-sensitive strategies."
      }
    ],
    "manual_overrides": [
      {
        "name": "force_pass",
        "how": "set config.force_verdict = pass for 60s",
        "when": "NEVER for open orders \u2014 rate bypass risks triggering CLOB 429 and blocking all order operations. Only use during a confirmed CLOB rate-limit policy change that has not yet propagated to config."
      },
      {
        "name": "reset_window_counter",
        "how": "polytraders bot reset-counter risk.rate_limit_governor --window trading",
        "when": "When the internal counter has drifted from the live CLOB state (e.g. after a bot restart). Requires Risk pod lead approval."
      }
    ],
    "healthcheck": "GET /internal/health/ratelimitgovernor \u2192 200 if trading window utilisation < 80%, last X-RateLimit-Remaining header read < 60s ago, and zero 429 responses in the last 5 minutes; red if utilisation >= 100%, any 429 observed, or header sync stale > 60s."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass including budget exhaustion, warning reshape, and priority cancel/flatten paths",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "Integration test: counter syncs correctly from mock X-RateLimit-Remaining headers",
        "how_measured": "Integration test suite",
        "threshold": "Pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "No 429 responses observed in 48h shadow run",
        "how_measured": "polytraders_risk_ratelimitgovernor_429_responses_total counter",
        "threshold": "0 events"
      },
      {
        "gate": "p99 evaluation latency < 5ms",
        "how_measured": "polytraders_risk_ratelimitgovernor_eval_latency_ms histogram",
        "threshold": "p99 < 5ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "Cancel and risk-flatten always pass even when trading budget is exhausted (7-day live evidence)",
        "how_measured": "reason_code audit on PRIORITY_CANCEL and PRIORITY_FLATTEN decisions",
        "threshold": "100% approval rate for cancel/flatten intents regardless of budget state"
      },
      {
        "gate": "Zero 429 responses over 7 consecutive days",
        "how_measured": "RateLimitGovernor429Observed alert history",
        "threshold": "0 firings"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "RiskVote"
    ],
    "topics": [
      "polytraders.reports.risk"
    ],
    "cadence": "every-event",
    "retention_class": "2y",
    "sampling_rule": "emit-every",
    "bus_failure_action": "fail-closed",
    "user_visible": "no",
    "consumes_kinds": []
  },
  "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"
  }
}