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 LayerIntelligence4.9 SourceOfTruthVerifier

4.9 SourceOfTruthVerifier

Intelligence Signal Service Read-only PLANNED Spec started capital · Indirect P2 · Data normalisation pending stub

Sanity-check that the source listed in a market's rule actually publishes what's needed for resolution.

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

LayerIntelligence  Intelligence
Bot classSignal Service
AuthorityRead-only
StatusPLANNED
ReadinessSpec started
Runs before
Runs after
Applies to
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

2. Purpose

Sanity-check that the source listed in a market's rule actually publishes what's needed for resolution.

3. Why This Bot Matters

  • Cited source does not actually publish what's needed

    A market that cites 'team-website.com' as its source-of-truth resolves on whatever that site publishes. If the site stops publishing, publishes only behind a paywall, or publishes too coarsely (winner only, no margin), the market cannot be resolved cleanly. Catching that at listing prevents the trade.

  • Rule change to a worse source goes undetected

    A post-listing edit can swap the cited source for a less reliable one. Pairing this verifier with RuleChangeMonitor catches the moment the cited source becomes unsuitable.

  • Operators have no defensible exclusion reason

    MarketScanner needs a structured reason to exclude a market with a broken source-of-truth. The verifier produces that reason in a form Discovery and Governance can both consume.

4. Required Polymarket Inputs

InputSourceRequired?Use
Market status and metadata from Gamma APIgammaYesFirst source in three-way verification.
Market status and metadata from Data APIdataYesSecond source in three-way verification.
On-chain condition state from CTFExchangeV2onchainYesThird source — authoritative on-chain resolution status.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesSuppress all emissions when KillSwitch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
poll_interval_s1206001800Seconds between three-source verification cycles per active market.
staleness_threshold_s3006003600Maximum age of any source response before verification is considered stale.

7. Detailed Parameter Instructions

poll_interval_s

What it means

Seconds between three-source verification cycles per active market.

Default

{ "poll_interval_s": 120 }

Why this default matters

120 s provides timely detection of source divergence without overloading Gamma, Data, and RPC endpoints.

Threshold logic

ConditionAction
interval <= 120 sNormal
120–600 sWARN — reduced divergence detection speed
> 1800 sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.poll_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

Market state is verified across multiple sources regularly to catch inconsistencies.

staleness_threshold_s

What it means

Maximum age of any source response before verification is considered stale.

Default

{ "staleness_threshold_s": 300 }

Why this default matters

300 s ensures all three sources are fresh before comparison.

Threshold logic

ConditionAction
age <= 300 sNormal
300–600 sWARN — approaching stale threshold
> 3600 sReject — emit STALE_DATA and halt verification

Developer check

if (source_age_s > p.staleness_threshold_s.hard) emit('STALE_DATA');

User-facing English

Source data must be fresh for verification to be reliable.

8. Default Configuration

{
  "bot_id": "intel.sourceoftruthverifier",
  "version": "0.1.0",
  "mode": "planned",
  "defaults": {
    "poll_interval_s": 120,
    "staleness_threshold_s": 300
  },
  "locked": {
    "poll_interval_s": {
      "max": 1800
    },
    "staleness_threshold_s": {
      "max": 3600
    }
  }
}

9. Implementation Flow

— not yet authored —

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 verifySourceOfTruth(condition_id):
  // 0. KillSwitch check
  IF FETCH internal.killswitch.status == ACTIVE:
    RETURN

  // 1. Fetch from all three sources
  gamma_state = FETCH gamma.GET('/markets/' + condition_id)
  data_state = FETCH data.GET('/markets/' + condition_id)
  chain_state = FETCH onchain.CTFExchangeV2.getCondition(condition_id)

  // 2. Compare resolution status
  IF gamma_state.status != data_state.status:
    EMIT WARN 'SOURCEOFTRUTH_GAMMA_DATA_MISMATCH'

  // 3. Compare on-chain vs data API
  IF chain_state.resolved != data_state.resolved:
    EMIT WARN 'SOURCEOFTRUTH_CHAIN_API_MISMATCH'

  // 4. Staleness check
  IF now() - gamma_state.updated_at > staleness_threshold_s:
    EMIT WARN 'STALE_DATA'

  // 5. Emit ObservationReport
  EMIT ObservationReport {
    report_id: gen_id(),
    kind: 'ObservationReport',
    condition_id: condition_id,
    verified: all_agree(gamma_state, data_state, chain_state),
    discrepancies: collect_discrepancies(),
    emitted_at_ms: now_ms()
  }

SDK calls used

  • gamma.GET('/markets/<condition_id>')
  • data.GET('/markets/<condition_id>')
  • onchain.CTFExchangeV2.getCondition(condition_id)

Complexity: O(1) per market; 3 parallel API calls per verification

11. Wire Examples

Input — what arrives on the wire

{
  "label": "Source-of-truth verification request for a condition",
  "source": "internal",
  "payload": {
    "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
    "trigger": "pre-strategy-entry",
    "timestamp_ms": 1746703000000
  }
}

Output — what the bot emits

{
  "label": "ObservationReport — sources agree",
  "payload": {
    "report_id": "rep_sotv_0xf1a2_1746703000000",
    "trace_id": "trc_0xbeef0102030405060710",
    "bot_id": "intel.sourceoftruthverifier",
    "kind": "ObservationReport",
    "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
    "verified": true,
    "discrepancies": [],
    "emitted_at_ms": 1746703003000
  }
}

12. Decision Logic

APPROVE

— not yet authored —

RESHAPE_REQUIRED

— not yet authored —

REJECT

— not yet authored —

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "report_id": "rep_sotv_0xf1a2_1746703000000",
  "trace_id": "trc_0xbeef0102030405060710",
  "bot_id": "intel.sourceoftruthverifier",
  "kind": "ObservationReport",
  "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
  "verified": true,
  "discrepancies": [],
  "emitted_at_ms": 1746703003000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
SOURCEOFTRUTH_GAMMA_DATA_MISMATCHWARNGamma API market status differs from Data API market status.Emit ObservationReport with discrepancy; flag for strategy review.Market state data sources are temporarily inconsistent.
SOURCEOFTRUTH_CHAIN_API_MISMATCHWARNOn-chain CTFExchangeV2 resolution state differs from Data API.Emit ObservationReport with chain_api_mismatch=true; alert on-call.On-chain market state differs from API — resolution may be pending propagation.
STALE_DATAWARNOne or more sources has not updated within staleness_threshold_s.Suppress emission; retry on next poll; alert if persists.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch active; all SourceOfTruthVerifier emissions suppressed.Continue polling but suppress all ObservationReport emissions.Verification signals paused while trading is suspended system-wide.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_intel_sourceoftruthverifier_observations_emitted_totalcountercountverifiedObservationReports emitted, broken down by verified (true/false).
polytraders_intel_sourceoftruthverifier_discrepancies_totalcountercountdiscrepancy_typeTotal discrepancy events detected by type.
polytraders_intel_sourceoftruthverifier_verification_latency_mshistogrammsTime to complete a three-source verification per market.

Alerts

AlertConditionSeverityRunbook
SourceOfTruthChainAPIMismatchrate(polytraders_intel_sourceoftruthverifier_discrepancies_total{discrepancy_type='chain_api'}[5m]) > 0page#runbook-sourceoftruth-chain-api-mismatch
SourceOfTruthGammaDataMismatchrate(polytraders_intel_sourceoftruthverifier_discrepancies_total{discrepancy_type='gamma_data'}[10m]) > 2warn#runbook-sourceoftruth-gamma-data-mismatch

Dashboards

  • Grafana — Intelligence / SourceOfTruthVerifier discrepancy rate and verification latency

16. Developer Reporting

{
  "bot_id": "intel.sourceoftruthverifier",
  "condition_id": "0xf1a2b30000000000000000000000000000000000000000000000000000000000",
  "verified": true,
  "discrepancies": [],
  "gamma_response_ms": 95,
  "data_response_ms": 110,
  "chain_response_ms": 180,
  "killswitch_active": false
}

17. Plain-English Reporting

SituationUser-facing explanation
Strategy paused entry pending source-of-truth verificationBefore entering a position, the system checks that all data sources agree on the market's current state. This ensures positions are not opened on inconsistent data.
Mismatch detected between on-chain state and APIThe on-chain market state differs from the API. This is usually a temporary propagation delay. The system paused new entries until sources agree.

18. Failure-Mode Block

main_failure_modeOn-chain RPC outage during a market resolution event causes the verifier to miss the chain-resolved status, potentially leaving strategies in positions on an already-resolved market.
false_positive_riskPropagation lag between on-chain resolution and Gamma/Data API updates causes a transient CHAIN_API_MISMATCH that resolves within minutes.
false_negative_riskAll three sources share the same cached CDN layer during a large-scale outage, causing the verifier to report agreement when all three are returning stale data.
safe_fallbackIf any source is unavailable for > staleness_threshold_s, emit STALE_DATA WARN and suppress new ObservationReports until all three sources return fresh data.
required_dependenciesPolymarket Gamma API, Polymarket Data API, Polygon RPC, KillSwitch active flag, Postgres for verification audit log

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
GAMMA_API_DOWNBlock Gamma API for 10 minAutomatic on Gamma recovery; re-verify all active markets
CHAIN_API_MISMATCHInject resolved=true in mock chain state while Data API shows resolved=falseAutomatic once both sources agree
KILL_SWITCH_ONSet killswitch.active=true during active verification pollingEmissions resume on KillSwitch reset

20. State & Persistence

Cold-start recovery

On cold start, reload from Postgres; re-verify all active markets on first cycle.

21. Concurrency & Idempotency

AspectSpecification
Execution modelasync per-market verification triggered by strategy entry or periodic poll
Max in-flight15
Idempotency keycondition_id + verification_cycle_id
Per-call timeout (ms)15000
Backpressure strategydrop-after-buffer — max 50 pending verifications queued
Locking / mutual exclusionPostgres advisory lock on condition_id during verification update

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchSuppress emissions when KillSwitch is active.
intel.resolutionruleparserConsume parsed resolution rules for cross-validation.

Emits to (downstream consumers)

BotWhyContract
strat.all_strategies

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
Polymarket Gamma APIhttps://gamma-api.polymarket.com99.9% / 500 ms p99
Polygon RPCPolygon mainnet99.9% / 200 ms p99

23. Security Surfaces

Abuse vectors considered

  • Attacker causes deliberate Gamma/chain mismatch to trigger strategy pauses via alert flooding
  • Stale on-chain state during fast resolution causes spurious CHAIN_API_MISMATCH alerts

Mitigations

  • Alert thresholds require multiple mismatches before paging to reduce noise
  • ObservationReports informational only — strategies verify independently before pausing

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsyes
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesCross-validates Gamma metadata against on-chain CTFExchangeV2 state and data API. All pUSD-denominated. No order signing.

API surfaces declared

gammadataonchaininternal

Networks supported

polygon

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
All three sources agree: verified=true ObservationReport emittedGamma, Data, and chain all return consistent resolved=false for active marketObservationReport emitted with verified=true, discrepancies=[]
Chain/API mismatch detected and reportedChain resolved=true; Data API resolved=falseObservationReport emitted with verified=false, discrepancies=['chain_api_mismatch']
KillSwitch suppresses emissionkillswitch.active=true; all sources availableNo ObservationReport; KILL_SWITCH_ACTIVE logged

Integration Tests

TestExpected result
Full lifecycle: verification triggers on strategy entry request; result consumed by strategyStrategy receives verified=true ObservationReport before entry
Gamma API down: STALE_DATA emitted; verification haltedSTALE_DATA WARN; no ObservationReports until Gamma recovers

Property Tests

PropertyRequired behaviour
SourceOfTruthVerifier never submits or signs ordersAlways true
No ObservationReport emitted when KillSwitch is activeAlways true

27. Operational Runbook

SourceOfTruthVerifier incidents are most critical when chain/API mismatch is detected — this may indicate a resolution propagation delay. Page immediately on chain mismatch.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
SourceOfTruthChainAPIMismatchCheck on-chain condition state via Polygon explorer. Compare with Data API response. If chain shows resolved, notify strategy team immediately.Intelligence pod lead immediately; escalate to market operations if > 5 min
SourceOfTruthGammaDataMismatchCheck last_updated_at for both Gamma and Data API responses. If one is stale, wait for propagation.Intelligence pod lead if mismatch persists > 10 min

Manual overrides

  • force_reverify_all — POST /internal/sourceoftruthverifier/reverify-all to force fresh three-source check on all active markets — After known Gamma or Data API outage recovery

Healthcheck

Endpoint: /internal/health/sourceoftruthverifier | Green: Last verification < 5 min ago AND Postgres reachable AND Gamma + RPC returning 200 | Red: No verification for > 15 min OR Postgres unreachable OR chain/API mismatch unresolved > 10 min

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 all three discrepancy types and KillSwitch suppressionCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Verification latency p99 < 3 s over 24 h on stagingpolytraders_intel_sourceoftruthverifier_verification_latency_ms histogramp99 < 3000 ms

Promote to General live

GateHow measuredThreshold
Zero missed mismatch detections over 7-day soak with synthetic injectionIntegration test log audit100% detection of injected mismatches

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