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.1 NewsIngest

4.1 NewsIngest

Intelligence Signal Service Read-only LIVE General live capital · Indirect P2 · Data normalisation pending stub

NewsIngest continuously pulls news from external feeds (RSS, partner APIs), resolves named entities to Polymarket market IDs via the Gamma API, and scores each story for materiality. High-materiality stories trigger immediate ObservationReport emission; routine items are sampled 1/10. NewsIngest is strictly read-only — it never submits, signs, or modifies orders. Output is consumed by news-materiality-trader and contradictiondetector.

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
StatusLIVE
ReadinessGeneral live
Runs beforenews-materiality-trader, contradictiondetector
Runs afterRSS / partner API fetch and dedup
Applies toAll live Polymarket markets with matching entities in watchlist
Default modegeneral_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Intelligence pod

2. Purpose

NewsIngest continuously pulls news from external feeds (RSS, partner APIs), resolves named entities to Polymarket market IDs via the Gamma API, and scores each story for materiality. High-materiality stories trigger immediate ObservationReport emission; routine items are sampled 1/10. NewsIngest is strictly read-only — it never submits, signs, or modifies orders. Output is consumed by news-materiality-trader and contradictiondetector.

3. Why This Bot Matters

  • High-materiality story missed

    news-materiality-trader fails to re-price after a major event; positions held through adverse resolution.

  • Entity resolution maps story to wrong market

    Materiality signal sent to unrelated market, polluting downstream strategies with false signal.

  • Duplicate story flooding the bus

    Contradictiondetector and materiality-trader overwhelmed with redundant events, latency spikes.

  • Stale feed accepted as fresh

    Old news recycled as new; obsolete materiality score triggers trades on stale information.

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
Market metadata including condition IDs, question text, and neg-risk flagsGamma APIYesResolve entity mentions in news stories to specific Polymarket market IDs.
Market resolution source and rules textGamma APINoDetermine whether a story is relevant to UMA-resolved vs partner-resolved markets.

5. Required Internal Inputs

InputSourceRequired?Use
Entity watchlist and market mappingStrategyRegistry / configYesFilter incoming stories to only those touching watched entities.
KillSwitch active flagKillSwitchYesSuppress ObservationReport emissions when KillSwitch is active; continue ingesting passively.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
materiality_min0.30.10.05Minimum materiality score (0–1) for a story to be forwarded. Stories below this threshold are silently dropped.
dedup_window_s3006010Seconds within which an identical story fingerprint is considered a duplicate and silently dropped.
source_priority['reuters', 'ap', 'bloomberg', 'league_feeds', 'official_handles']NoneNoneOrdered list of feed sources; earlier entries get higher materiality weight.
emit_every_above0.70.50.4Materiality score above which every story is emitted (no sampling). Below this, 1/10 sampling applies.

7. Detailed Parameter Instructions

materiality_min

What it means

Minimum materiality score (0–1) for a story to be forwarded. Stories below this threshold are silently dropped.

Default

{ "materiality_min": 0.3 }

Why this default matters

A threshold of 0.3 balances signal noise — lower values flood downstream bots with irrelevant items.

Threshold logic

ConditionAction
score ≥ 0.3Emit ObservationReport
0.1–0.3WARN — borderline; include with LOW_MATERIALITY flag
< 0.05Hard drop — NEWSINGEST_BELOW_MATERIALITY_FLOOR

Developer check

if (story.score < params.hard) drop('NEWSINGEST_BELOW_MATERIALITY_FLOOR');

User-facing English

Only stories judged sufficiently relevant to your markets are forwarded.

dedup_window_s

What it means

Seconds within which an identical story fingerprint is considered a duplicate and silently dropped.

Default

{ "dedup_window_s": 300 }

Why this default matters

300 s suppresses wire-service re-transmissions without missing genuine updates.

Threshold logic

ConditionAction
window ≥ 300 sStandard dedup
60–300 sWARN — tighter window; risk of some duplicates
< 10 sReject config change — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.dedup_window_s < p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

Duplicate news items are suppressed automatically.

source_priority

What it means

Ordered list of feed sources; earlier entries get higher materiality weight.

Default

{ "source_priority": ["reuters", "ap", "bloomberg", "league_feeds", "official_handles"] }

Why this default matters

Wire services carry higher factual confidence than unofficial handles.

Threshold logic

— not yet authored —

Developer check

// not yet authored

User-facing English

Higher-confidence sources influence opportunity scores more than social feeds.

emit_every_above

What it means

Materiality score above which every story is emitted (no sampling). Below this, 1/10 sampling applies.

Default

{ "emit_every_above": 0.7 }

Why this default matters

High-materiality stories (≥0.7) must never be dropped by the sampler.

Threshold logic

ConditionAction
score ≥ 0.7emit-every — no sampling
0.3–0.7sample-1/10
< 0.4Cannot lower emit_every threshold without Risk review

Developer check

if (p.emit_every_above < p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

Breaking news is always forwarded in full; routine updates are sampled.

8. Default Configuration

{
  "bot_id": "intel.newsingest",
  "version": "2.1.0",
  "mode": "general_live",
  "defaults": {
    "materiality_min": 0.3,
    "dedup_window_s": 300,
    "source_priority": [
      "reuters",
      "ap",
      "bloomberg",
      "league_feeds",
      "official_handles"
    ],
    "emit_every_above": 0.7
  },
  "locked": {
    "dedup_window_s": {
      "min": 10
    },
    "emit_every_above": {
      "min": 0.4
    }
  }
}

9. Implementation Flow

  1. Subscribe to all configured external feeds (RSS polling + partner API long-poll or webhook).
  2. On each incoming story: compute a 64-bit fingerprint (source + headline hash). Check dedup_window_s ring buffer; if duplicate, drop silently.
  3. Perform entity extraction (NER); resolve entities against Gamma API market metadata to produce a list of candidate condition_ids.
  4. If no condition_id matched, drop story with NEWSINGEST_NO_MARKET_MATCH.
  5. Score materiality (0–1) based on: source priority weight × entity confidence × recency decay × neg-risk amplifier if any matched market has neg_risk=true.
  6. If score < materiality_min hard floor, drop with NEWSINGEST_BELOW_MATERIALITY_FLOOR.
  7. Check KillSwitch; if active, ingest and score but suppress ObservationReport emissions.
  8. Apply sampling rule: if score ≥ emit_every_above, emit every time; else sample 1/10.
  9. Emit ObservationReport with: report_id, trace_id, condition_ids, materiality_score, story_summary, source, pub_ts_ms, neg_risk_flag, and sampling_applied.
  10. Log ingest cycle summary: stories_received, deduped, no_match, below_floor, sampled_out, emitted.

10. Reference Implementation

Subscribes to external feeds, fingerprints each story for dedup, resolves entities to Polymarket condition_ids via Gamma API, scores materiality, applies sampling, and emits ObservationReport for stories that pass all gates.

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output.

FUNCTION ingestFeed(feed_event):
  // --- 0. Dedup ---
  fp = fingerprint(feed_event.source + feed_event.headline)
  IF fp IN dedup_ring_buffer(dedup_window_s):
    LOG DEBUG 'NEWSINGEST_DEDUP_DROP'
    RETURN
  dedup_ring_buffer.add(fp, ttl=dedup_window_s)

  // --- 1. KillSwitch gate ---
  ks = FETCH internal.killswitch.status
  killswitchActive = ks.active

  // --- 2. Entity extraction + market resolution ---
  entities = extractEntities(feed_event.body)
  condition_ids = []
  FOR entity IN entities:
    markets = gamma_api.GET('/markets?keyword=' + entity.canonical)
    FOR m IN markets:
      IF m.active AND NOT m.closed:
        condition_ids.append(m.condition_id)

  IF condition_ids IS EMPTY:
    LOG DEBUG 'NEWSINGEST_NO_MARKET_MATCH'
    RETURN

  // --- 3. Materiality scoring ---
  source_weight = SOURCE_WEIGHTS[feed_event.source] OR 0.5
  entity_conf   = AVG(entity.confidence FOR entity IN entities)
  recency_decay = exp(-age_s / 600)         // half-life 10 min
  neg_risk_amp  = 1.2 IF any(m.neg_risk FOR m IN markets) ELSE 1.0
  score = source_weight * entity_conf * recency_decay * neg_risk_amp

  IF score < params.materiality_min.hard:
    LOG DEBUG 'NEWSINGEST_BELOW_MATERIALITY_FLOOR'
    RETURN

  // --- 4. Warning annotation ---
  warnings = []
  IF score < params.materiality_min.default:
    warnings.append('NEWSINGEST_LOW_MATERIALITY')

  // --- 5. Sampling ---
  IF score >= params.emit_every_above:
    sampling_applied = false
  ELSE:
    IF random() > 0.1:                      // sample-1/10
      RETURN
    sampling_applied = true

  // --- 6. KillSwitch suppress ---
  IF killswitchActive:
    LOG INFO 'KILL_SWITCH_ACTIVE — suppressing ObservationReport'
    RETURN

  // --- 7. Emit ---
  report = ObservationReport(
    report_id        = 'rep_ni_' + condition_ids[0][:6] + '_' + now_ms(),
    trace_id         = newTraceId(),
    bot_id           = 'intel.newsingest',
    kind             = 'ObservationReport',
    condition_ids    = condition_ids,
    materiality_score= score,
    story_summary    = feed_event.headline[:200],
    source           = feed_event.source,
    pub_ts_ms        = feed_event.pub_ts_ms,
    neg_risk_flag    = neg_risk_amp > 1.0,
    sampling_applied = sampling_applied,
    warnings         = warnings,
    emitted_at_ms    = now_ms()
  )
  EMIT internal.bus.observations <- report

SDK calls used

  • gamma_api.GET('/markets?keyword=<entity>')
  • gamma_api.GET('/market/<condition_id>')

Complexity: O(E × M) per story, where E = extracted entities, M = live markets per entity

11. Wire Examples

Input — what arrives on the wire

{
  "label": "Incoming AP wire story",
  "source": "ap",
  "payload": {
    "source": "ap",
    "headline": "Federal Reserve holds rates at 4.25–4.50% at May 2026 meeting",
    "body": "The Federal Open Market Committee voted unanimously to hold...",
    "pub_ts_ms": 1746700800000,
    "feed": "rss"
  }
}

Output — what the bot emits

{
  "label": "ObservationReport — high-materiality story",
  "payload": {
    "report_id": "rep_ni_0xabc1_1746700800000",
    "trace_id": "trc_0xdeadbeef01020304050607",
    "bot_id": "intel.newsingest",
    "kind": "ObservationReport",
    "condition_ids": [
      "0xabc1230000000000000000000000000000000000000000000000000000000000"
    ],
    "materiality_score": 0.82,
    "story_summary": "Federal Reserve holds rates at 4.25–4.50% at May 2026 meeting",
    "source": "ap",
    "pub_ts_ms": 1746700800000,
    "neg_risk_flag": false,
    "sampling_applied": false,
    "warnings": [],
    "emitted_at_ms": 1746700800452
  }
}

12. Decision Logic

APPROVE

Not applicable — NewsIngest does not issue approval votes. It emits ObservationReports for stories that pass dedup, entity resolution, and materiality thresholds.

RESHAPE_REQUIRED

Not applicable — NewsIngest is read-only.

REJECT

Stories are dropped (not emitted) for: duplicate fingerprint (NEWSINGEST_DEDUP_DROP), no market match (NEWSINGEST_NO_MARKET_MATCH), below materiality floor (NEWSINGEST_BELOW_MATERIALITY_FLOOR), or KillSwitch active (KILL_SWITCH_ACTIVE).

WARNING_ONLY

Stories with materiality between warning and default thresholds are included with a LOW_MATERIALITY flag. Downstream bots decide whether to act.

13. Standard Decision Output

This bot returns a ObservationReport object. See ObservationReport schema.

{
  "report_id": "rep_ni_0xabc1_1746700800000",
  "trace_id": "trc_0xdeadbeef01020304",
  "bot_id": "intel.newsingest",
  "kind": "ObservationReport",
  "condition_ids": [
    "0xabc1230000000000000000000000000000000000000000000000000000000000"
  ],
  "materiality_score": 0.82,
  "story_summary": "AP: Fed holds rates unchanged at May 2026 meeting",
  "source": "ap",
  "pub_ts_ms": 1746700800000,
  "neg_risk_flag": false,
  "sampling_applied": false,
  "warnings": [],
  "emitted_at_ms": 1746700800450
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
NEWSINGEST_DEDUP_DROPINFOStory fingerprint matched a recent entry in the dedup ring buffer.Silently drop; log at DEBUG level.
NEWSINGEST_NO_MARKET_MATCHINFOEntity extraction produced no condition_ids from Gamma API.Drop story; no ObservationReport emitted.
NEWSINGEST_BELOW_MATERIALITY_FLOORINFOStory materiality score below hard floor; irrelevant to any open position.Drop story silently.
NEWSINGEST_LOW_MATERIALITYWARNStory score between warning and default thresholds; forwarded with flag.Emit ObservationReport with LOW_MATERIALITY warning; downstream bots decide.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch is active; ObservationReport emissions suppressed.Continue ingesting and scoring but do not emit.News signals are paused while trading is suspended system-wide.
STALE_DATAWARNGamma API unavailable; entity resolution halted for this cycle.Buffer incoming stories up to dedup_window_s; halt emissions until API recovers.
MARKET_CLOSEDEXPLAINEntity resolved to a market that is already closed or resolved.Skip that condition_id; continue processing remaining matched markets.
NEWSINGEST_FEED_UNREACHABLEWARNAn external feed (RSS / partner API) is unreachable.Log WARN; continue processing other feeds; alert if all feeds unreachable.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_intel_newsingest_stories_received_totalcountercountsourceTotal raw stories received from all feeds, per source.
polytraders_intel_newsingest_observations_emitted_totalcountercountsource, sampling_appliedObservationReport items emitted to the internal bus.
polytraders_intel_newsingest_drop_totalcountercountreason_codeStories dropped, broken down by drop reason code.
polytraders_intel_newsingest_materiality_scorehistogramratioDistribution of materiality scores for all stories that passed entity resolution.
polytraders_intel_newsingest_entity_resolution_latency_mshistogrammsWall-clock time for Gamma API entity resolution per story.
polytraders_intel_newsingest_feed_lag_sgaugesecondssourceAge of the most recent story received from each feed source.

Alerts

AlertConditionSeverityRunbook
NewsIngestAllFeedsDownrate(polytraders_intel_newsingest_stories_received_total[5m]) == 0page#runbook-newsingest-feeds-down
NewsIngestGammaAPIDownrate(polytraders_intel_newsingest_entity_resolution_latency_ms_count[5m]) == 0 AND rate(polytraders_intel_newsingest_stories_received_total[5m]) > 0page#runbook-newsingest-gamma-api-down
NewsIngestHighDropRaterate(polytraders_intel_newsingest_drop_total[10m]{reason_code='NEWSINGEST_NO_MARKET_MATCH'}) / rate(polytraders_intel_newsingest_stories_received_total[10m]) > 0.95warn#runbook-newsingest-entity-mismatch
NewsIngestFeedLagpolytraders_intel_newsingest_feed_lag_s{source='reuters'} > 120warn#runbook-newsingest-feed-lag

Dashboards

  • Grafana — Intelligence / NewsIngest feed health
  • Grafana — Intelligence / materiality score distribution

16. Developer Reporting

{
  "bot_id": "intel.newsingest",
  "cycle_ts_ms": 1746700800000,
  "stories_received": 47,
  "deduped": 12,
  "no_market_match": 8,
  "below_floor": 5,
  "sampled_out": 19,
  "emitted": 3,
  "top_materiality": 0.82,
  "killswitch_active": false
}

17. Plain-English Reporting

SituationUser-facing explanation
Fewer news signals than expectedMany stories were filtered out because they did not match any tracked market, fell below the materiality threshold, or were duplicates of items already processed.
News signal delayed relative to headline timeStories go through entity resolution and materiality scoring before being forwarded. High-materiality breaking news is forwarded immediately; routine items may be sampled.
No signals during KillSwitchNews ingestion continues in the background, but no signals are forwarded while trading is paused system-wide.

18. Failure-Mode Block

main_failure_modeEntity resolution incorrectly maps a high-materiality story to a wrong condition_id, causing downstream strategies to act on a market that is not actually affected.
false_positive_riskA story with a borderline materiality score is forwarded with LOW_MATERIALITY flag and triggers a trade that would not have passed a stricter threshold.
false_negative_riskA high-materiality story is dropped due to an entity resolution miss — the story mentions an entity in shorthand not in the watchlist dictionary.
safe_fallbackIf Gamma API is unavailable, entity-to-market resolution halts and no ObservationReports are emitted for that feed cycle. Stories are buffered up to dedup_window_s; if API recovers within that window, they are re-processed.
required_dependenciesExternal feeds (RSS / partner APIs) reachable, Gamma API for entity-to-market resolution, KillSwitch active flag readable

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
STALE_FEEDDisconnect Reuters RSS for 120 sAutomatic when feed reconnects within dedup_window_s; buffered stories re-processed
GAMMA_API_DOWNBlock TCP to gamma-api.polymarket.comAutomatic on Gamma API recovery; buffered stories re-processed
HIGH_VOLUME_FLOODInject 1 000 unique stories/s for 10 sAutomatic when ingestion rate drops below queue drain rate
KILL_SWITCH_ONSet killswitch.active=trueEmissions resume on first event after KillSwitch reset
ENTITY_RESOLUTION_MISSSubmit story mentioning a real market entity not in the watchlist dictionaryAdd entity alias to watchlist; next occurrence resolved correctly

20. State & Persistence

Cold-start recovery

On cold start, dedup buffer is empty; the first dedup_window_s of operation may emit some near-duplicates from wire re-transmissions.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight10
Idempotency keystory fingerprint
Per-call timeout (ms)3000
Backpressure strategydrop-after-buffer — excess stories dropped when internal queue > 500 items
Locking / mutual exclusionnone — dedup ring buffer access is single-threaded

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate determines whether ObservationReports are emitted.

Emits to (downstream consumers)

BotWhyContract
strat.news_materiality_trader
strat.contradiction_detector

External services

ServiceEndpointSLA assumedOn failure
Reuters RSS / AP wirebest-effort; typically < 5 s from event to wire
Bloomberg headline API99.5% / 1 s p99
Gamma APIhttps://gamma-api.polymarket.com99.9% / 500 ms p99

23. Security Surfaces

Abuse vectors considered

  • Malicious external feed injecting a crafted headline that resolves to a high-value condition_id with artificially elevated materiality score
  • Entity resolution poisoning — an entity alias added to the watchlist that maps news about an unrelated topic to a targeted market

Mitigations

  • condition_id format validated against known 32-byte hex pattern before inclusion
  • Gamma API responses cross-checked against local market ID whitelist
  • All ObservationReports are recommendations only — downstream guardrails independently re-validate

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
NotesNewsIngest reads Gamma API negRisk and enableNegRisk fields to amplify materiality scores for stories touching multi-outcome negative-risk markets; volume figures in ObservationReport payloads are denominated in pUSD.

API surfaces declared

gammainternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation2.1.0
schema2
released2026-04-28

Migration history

DateFromToReasonAction taken
2026-04-28v1v2CLOB V2 cutover — collateral denomination changeMateriality score and volume references updated from USDC.e to pUSD. Gamma API queries updated to include enableNegRisk flag for neg-risk market detection. No signed-order plumbing in this bot; no feeRateBps or nonce fields to remove.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Duplicate story is dropped within dedup_window_sInject same headline twice within 60 sSecond occurrence silently dropped; NEWSINGEST_DEDUP_DROP logged
Story below materiality hard floor is droppedstory.score=0.03, hard=0.05Story dropped with NEWSINGEST_BELOW_MATERIALITY_FLOOR
High-materiality story emitted without samplingstory.score=0.82 ≥ emit_every_above=0.7ObservationReport emitted with sampling_applied=false
Routine story sampled 1/10story.score=0.4, emit_every_above=0.7; run 10 storiesApproximately 1 ObservationReport emitted; sampling_applied=true on remainder
KillSwitch suppresses emissionskillswitch.active=true; high-materiality story arrivesStory scored but no ObservationReport emitted; KILL_SWITCH_ACTIVE logged
No market match drops storyStory entities not in Gamma API watchlistDrop with NEWSINGEST_NO_MARKET_MATCH

Integration Tests

TestExpected result
AP wire story reaches contradictiondetector and news-materiality-traderObservationReport with correct condition_id propagates to both downstream bots within 2 s
Gamma API unavailability halts emission with bufferingNo ObservationReports emitted during outage; stories buffered; re-processed on recovery
neg-risk market amplifies materiality scoreStory matched to neg-risk market has materiality_score elevated and neg_risk_flag=true in output

Property Tests

PropertyRequired behaviour
NewsIngest never submits, signs, or modifies any orderAlways true
No ObservationReport emitted when KillSwitch is activeAlways true
No ObservationReport emitted when Gamma API is unavailableAlways true — missing entity resolution halts emissions

27. Operational Runbook

NewsIngest incidents are usually external feed outages or Gamma API slowness. The bot is read-only so incidents do not affect active positions — only new signal discovery.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
NewsIngestAllFeedsDown
NewsIngestGammaAPIDown
NewsIngestHighDropRate
NewsIngestFeedLag

Manual overrides

Healthcheck

GET /internal/health/newsingest -> 200 if Last story received < 60 s ago AND Gamma API responding AND dedup buffer writable. RED if All feeds silent > 120 s OR Gamma API errors > 50% for 5 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 dedup, materiality scoring, and KillSwitch suppressionCI test run100% pass
Gamma API entity resolution integration test: known entity resolves to correct condition_idIntegration testPass

Promote to Limited live

GateHow measuredThreshold
Entity resolution latency p99 < 500 ms over 24 hpolytraders_intel_newsingest_entity_resolution_latency_ms histogramp99 < 500 ms
neg-risk materiality amplifier produces correct neg_risk_flag in outputIntegration test with known neg-risk market entityPass

Promote to General live

GateHow measuredThreshold
Zero false NEWSINGEST_NO_MARKET_MATCH for entities in the curated watchlist over 7 daysGrafana drop_total metric filtered by reason_code< 1% miss rate on watchlist entities
KillSwitch suppression verified: zero ObservationReports emitted when KillSwitch activeIntegration testPass

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