Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerRisk1.16 ManualOverrideAuditor

1.16 ManualOverrideAuditor

Risk Guardrail Veto PLANNED Planned capital · Direct P4 · Core risk pending stub

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.

v3 readiness

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerRisk  Risk
Bot classGuardrail
AuthorityVeto
StatusPLANNED
ReadinessPlanned
Runs beforeOverride action applied
Runs afterOverride request submitted
Applies toEvery manual override request for any guardrail — requires justification, rate-limits overrides, and emits an immutable audit RiskVote for every request
Default modeplanned
User-visiblesummary-only
Developer ownerPolytraders core — Risk pod

Operational profile

Modes supportedquarantine

2. 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.

3. Why This Bot Matters

  • Silent override of a guardrail

    Without audit enforcement, a guardrail can be bypassed without trace, removing the protective layer without any record for post-hoc review.

  • Override rate limit bypassed

    Repeated overrides in a short window can be used to trade in conditions that guardrails are designed to block, effectively disabling the risk controls.

No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.

4. Required Polymarket Inputs

InputSourceRequired?Use
None — ManualOverrideAuditor does not read Polymarket APIsinternalNoAll inputs come from internal override request payloads.

5. Required Internal Inputs

InputSourceRequired?Use
Override request payload (target guardrail, justification, requestor_id)internalYesValidate justification is non-empty, check rate limit for requestor, and record the override attempt.
Override rate limit counter (per requestor per time window)internalYesEnforce max_overrides_per_window to prevent override abuse.
KillSwitch active flagKillSwitchYesIf active, reject all override requests immediately.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_overrides_per_window323Maximum number of manual override approvals allowed per requestor in the override_window_minutes period.
override_window_minutes60NoneNoneTime window in minutes over which the override count is tracked per requestor.
require_justificationTrueNoneTrueWhen true, override requests with empty or missing justification strings are hard-rejected.

7. Detailed Parameter Instructions

max_overrides_per_window

What it means

Maximum number of manual override approvals allowed per requestor in the override_window_minutes period.

Default

{ "max_overrides_per_window": 3 }

Why this default matters

3 overrides per window is enough for legitimate operational needs while preventing systematic abuse.

Threshold logic

ConditionAction
override_count < 2APPROVE
override_count == 2WARN — OVERRIDE_AUDITOR_RATE_APPROACHING
override_count >= 3REJECT — OVERRIDE_AUDITOR_RATE_EXCEEDED

Developer check

if (overrideCount >= params.max_overrides_per_window) return reject('OVERRIDE_AUDITOR_RATE_EXCEEDED');

User-facing English

You have reached the maximum number of overrides allowed in this time window.

override_window_minutes

What it means

Time window in minutes over which the override count is tracked per requestor.

Default

{ "override_window_minutes": 60 }

Why this default matters

A 60-minute window aligns with operational shifts and provides meaningful rate-limiting without being too restrictive for incident response.

Threshold logic

ConditionAction
alwaysCount overrides within sliding 60-minute window per requestor

Developer check

const windowStart = now_ms() - params.override_window_minutes * 60000;

User-facing English

— not yet authored —

require_justification

What it means

When true, override requests with empty or missing justification strings are hard-rejected.

Default

{ "require_justification": true }

Why this default matters

Requiring justification ensures every override has a documented rationale for post-hoc audit.

Threshold logic

ConditionAction
require_justification=true AND justification is emptyREJECT — OVERRIDE_AUDITOR_NO_JUSTIFICATION
justification is non-empty OR require_justification=falseAPPROVE (this check)

Developer check

if (params.require_justification && !override.justification.trim()) return reject('OVERRIDE_AUDITOR_NO_JUSTIFICATION');

User-facing English

A justification is required for manual overrides.

8. Default Configuration

{
  "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
    }
  }
}

9. Implementation Flow

  1. Receive override request payload: target_guardrail, requestor_id, justification, timestamp_ms.
  2. Check KillSwitch; if active, REJECT all overrides immediately.
  3. If require_justification=true and justification is empty, HARD_REJECT(OVERRIDE_AUDITOR_NO_JUSTIFICATION).
  4. Load override count for requestor_id within the last override_window_minutes from Redis.
  5. If override_count >= max_overrides_per_window, HARD_REJECT(OVERRIDE_AUDITOR_RATE_EXCEEDED).
  6. If override_count == max_overrides_per_window - 1, attach WARN(OVERRIDE_AUDITOR_RATE_APPROACHING).
  7. Emit immutable audit RiskVote with requestor_id, target_guardrail, justification, and timestamp.
  8. Increment override counter for requestor_id in Redis (with window_minutes TTL).
  9. Return APPROVE to allow the override to proceed.

10. Reference Implementation

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.

FUNCTION evaluateOverride(request):
  ks = FETCH internal.killswitch.status
  IF ks.active:
    EMIT RiskVote(HARD_REJECT, KILL_SWITCH_ACTIVE); RETURN

  IF params.require_justification AND NOT request.justification.strip():
    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_NO_JUSTIFICATION); RETURN

  windowStart = now_ms() - params.override_window_minutes * 60000
  counter = FETCH redis.zcount(request.requestor_id, windowStart, now_ms())
  IF counter IS NULL:
    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_DATA_UNAVAILABLE); RETURN

  IF counter >= params.max_overrides_per_window:
    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_RATE_EXCEEDED); RETURN

  IF counter == params.max_overrides_per_window - 1:
    annotations.append(WARN(OVERRIDE_AUDITOR_RATE_APPROACHING))

  // Write audit record atomically before approving
  ok = WRITE redis.xadd('audit_overrides', {
    requestor_id: request.requestor_id,
    target_guardrail: request.target_guardrail,
    justification: request.justification,
    timestamp_ms: now_ms()
  })
  IF NOT ok:
    EMIT RiskVote(HARD_REJECT, OVERRIDE_AUDITOR_DATA_UNAVAILABLE); RETURN

  redis.zadd(request.requestor_id, now_ms(), now_ms())
  EMIT RiskVote(APPROVE, audit_id=ok.entry_id)

SDK calls used

  • redis.zcount(requestor_id, windowStart, now)
  • redis.xadd('audit_overrides', record)
  • redis.zadd(requestor_id, score, member)
  • internal.killswitch.status()

Complexity: O(1) — constant Redis ops

11. Wire Examples

Input — what arrives on the wire

Override request — rate limit exceededinternal

{
  "override_request_id": "ovr_b8c9d0e1f2a30008",
  "requestor_id": "ops_user_001",
  "target_guardrail": "risk.liquidity_guard",
  "justification": "Incident response — book feed down",
  "timestamp_ms": 1746800000000
}

Output — what the bot emits

RiskVote — HARD_REJECT rate exceeded

{
  "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"
}

12. 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 — either approved or rejected.

REJECT

Justification is missing, rate limit exceeded, or KillSwitch active.

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "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"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch active; no overrides allowed.Immediate HARD_REJECT.Override requests are blocked while trading is paused.
OVERRIDE_AUDITOR_NO_JUSTIFICATIONHARD_REJECTOverride request missing required justification string.HARD_REJECT; do not emit override counter increment.A justification is required for all manual override requests.
OVERRIDE_AUDITOR_RATE_EXCEEDEDHARD_REJECTRequestor has exceeded the max_overrides_per_window limit.HARD_REJECT; do not emit override counter increment.You have exceeded the override limit for this time window.
OVERRIDE_AUDITOR_RATE_APPROACHINGWARNOverride count is one below the hard limit.Attach WARN annotation; APPROVE.
OVERRIDE_AUDITOR_DATA_UNAVAILABLEHARD_REJECTRedis override counter unavailable; cannot enforce rate limit or record audit.HARD_REJECT (fail-closed).Override system temporarily unavailable. Please try again.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_risk_manualoverrideauditor_decisions_totalcountercountdecision, reason_code, target_guardrailTotal override decisions by type, reason, and target guardrail.
polytraders_risk_manualoverrideauditor_overrides_per_windowgaugecountrequestor_idCurrent override count per requestor within the rolling window.
polytraders_risk_manualoverrideauditor_eval_latency_mshistogrammillisecondsLatency from request receipt to RiskVote emit.

Alerts

AlertConditionSeverityRunbook
OverrideAuditorRateExceededrate(polytraders_risk_manualoverrideauditor_decisions_total{reason_code='OVERRIDE_AUDITOR_RATE_EXCEEDED'}[5m]) > 0P1#runbook-overrideauditor-rate
OverrideAuditorDataUnavailablerate(polytraders_risk_manualoverrideauditor_decisions_total{reason_code='OVERRIDE_AUDITOR_DATA_UNAVAILABLE'}[5m]) > 0P1#runbook-overrideauditor-data

16. Developer Reporting

{
  "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"
}

17. Plain-English Reporting

SituationUser-facing explanation
Override blocked — rate limitYou have submitted too many overrides in the current time window. Please wait before submitting another override request.
Override blocked — no justificationA justification is required for all manual override requests. Please provide a reason before proceeding.
Override approved — audit recordedYour override request has been approved and an audit record has been created. All overrides are logged for review by the risk team.

18. Failure-Mode Block

main_failure_modeFailing to record the override audit entry due to a Redis write failure, allowing a silent override.
false_positive_riskRate counter includes expired entries due to a clock skew, causing a false rate-limit rejection.
false_negative_riskConcurrent override requests from the same requestor being processed before the counter is incremented, allowing more overrides than the limit.
safe_fallbackIf the Redis override counter is unavailable, HARD_REJECT with OVERRIDE_AUDITOR_DATA_UNAVAILABLE. Never approve when the counter cannot be read or written.
required_dependenciesRedis override counter store, KillSwitch active flag, Audit log write path

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
REDIS_UNAVAILABLEBlock TCP to Redis clusterReturns to normal within one cycle after Redis is restored; counter rebuilt from audit stream.
RATE_LIMIT_EXCEEDEDSubmit 3 overrides in 60 minutes for the same requestor_idReturns to APPROVE after the rolling window expires.
EMPTY_JUSTIFICATIONSubmit override request with justification=''Immediate on resubmission with non-empty justification.

20. State & Persistence

Cold-start recovery

Counter rebuilt from audit stream on cold start. If Redis unavailable, HARD_REJECT until restored.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight50
Idempotency keyoverride_request_id
Per-call timeout (ms)50
Backpressure strategydrop newest
Locking / mutual exclusionRedis INCR for counter ensures atomic increment; audit stream uses XADD for append-only safety

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchAll overrides blocked when kill switch is active.HARD_REJECT(KILL_SWITCH_ACTIVE) on every override request when active.

Emits to (downstream consumers)

BotWhyContract
internal.audit_logEvery override request (approved or rejected) emits an immutable audit RiskVote to the audit log.Audit record written before APPROVE is returned; write failure triggers HARD_REJECT.

External services

ServiceEndpointSLA assumedOn failure
Redis (override counter + audit stream)internal Redis cluster99.99% (in-cluster)HARD_REJECT(OVERRIDE_AUDITOR_DATA_UNAVAILABLE) if Redis unavailable.

23. Security Surfaces

Abuse vectors considered

  • 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

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesManualOverrideAuditor does not interact with CLOB or Polymarket directly; all inputs are internal override request payloads.

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Approve valid override with justification and within rate limitoverride_count=1, max=3, justification='Incident response'APPROVE with audit record emitted
Reject when justification is emptyjustification=''HARD_REJECT(OVERRIDE_AUDITOR_NO_JUSTIFICATION)
Reject when rate limit exceededoverride_count=3, max=3HARD_REJECT(OVERRIDE_AUDITOR_RATE_EXCEEDED)
Warn when approaching rate limitoverride_count=2, max=3APPROVE with WARN annotation

Integration Tests

TestExpected result
Override counter increments atomically for concurrent requestsAt most max_overrides_per_window overrides approved in any 60-minute window for the same requestor
Audit record immutably stored and retrievable after overrideAudit record queryable from audit log with requestor_id, target_guardrail, and justification

Property Tests

PropertyRequired behaviour
Empty justification never results in APPROVE when require_justification=trueAlways true
Audit record emitted for every APPROVE and HARD_REJECTAlways true — audit log is the primary compliance artefact

27. Operational Runbook

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.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
OverrideAuditorRateExceededIdentify requestor_id from the reason_code log; review override audit stream to determine if requests are legitimate incident response or potential abuse.Risk pod lead immediately; review override audit records.
OverrideAuditorDataUnavailableCheck Redis cluster connectivity; confirm audit stream is writable.Infra on-call if Redis unavailable > 1 minute.

Manual overrides

  • polytraders risk reset-override-counter --requestor-id <id> — After a confirmed legitimate incident response where the rate limit was exceeded; requires risk pod lead written approval.

Healthcheck

GET /internal/health/manualoverrideauditor → green: Redis reachable, audit stream writable, override counter TTL active; red: Redis unreachable, audit stream write failure, or counter TTL expired

28. Promotion Gates

A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.

Promote to Shadow

GateHow measuredThreshold
Unit tests pass for rate limit, justification, and Redis failure scenariosCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Audit records verified immutable in Redis stream over 48h shadow runAudit log audit by risk team100% records present and unmodified

Promote to General live

GateHow measuredThreshold
Zero DATA_UNAVAILABLE rejections during normal hours over 7 daysOverrideAuditorDataUnavailable alert history0 firings

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
Purpose defined✓ done
Required inputs listed✓ done
Parameters defined✓ done
Defaults defined✓ done
Warning thresholds defined✓ done
Hard thresholds defined✓ done
Safe fallback defined✓ done
Structured output defined✓ done
Developer log defined✓ done
Plain-English explanation✓ done
Unit tests defined✓ done
Integration tests defined✓ done
Property tests defined✓ done
Failure-mode block complete✓ done
Reference implementation pseudocode✓ done
Wire examples (input + output)✓ done
Reason codes listed✓ done
Metrics & logs defined✓ done
State & persistence defined✓ done
Concurrency & idempotency defined✓ done
Dependencies declared✓ done
Security surfaces declared✓ done
Polymarket V2 compatibility declared✓ done
Version & migration history declared✓ done
Operational runbook defined✓ done
Promotion gates defined✓ done
Failure-injection recipes defined✓ done