4.1 NewsIngest
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
A bot is done when all four scores are. What does done mean?
1. Bot Identity
| Layer | Intelligence Intelligence |
|---|---|
| Bot class | Signal Service |
| Authority | Read-only |
| Status | LIVE |
| Readiness | General live |
| Runs before | news-materiality-trader, contradictiondetector |
| Runs after | RSS / partner API fetch and dedup |
| Applies to | All live Polymarket markets with matching entities in watchlist |
| Default mode | general_live |
| User-visible | Advanced details only |
| Developer owner | Polytraders 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
| Input | Source | Required? | Use |
|---|---|---|---|
| Market metadata including condition IDs, question text, and neg-risk flags | Gamma API | Yes | Resolve entity mentions in news stories to specific Polymarket market IDs. |
| Market resolution source and rules text | Gamma API | No | Determine whether a story is relevant to UMA-resolved vs partner-resolved markets. |
5. Required Internal Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| Entity watchlist and market mapping | StrategyRegistry / config | Yes | Filter incoming stories to only those touching watched entities. |
| KillSwitch active flag | KillSwitch | Yes | Suppress ObservationReport emissions when KillSwitch is active; continue ingesting passively. |
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|---|---|---|---|
| materiality_min | 0.3 | 0.1 | 0.05 | Minimum materiality score (0–1) for a story to be forwarded. Stories below this threshold are silently dropped. |
| dedup_window_s | 300 | 60 | 10 | Seconds within which an identical story fingerprint is considered a duplicate and silently dropped. |
| source_priority | ['reuters', 'ap', 'bloomberg', 'league_feeds', 'official_handles'] | None | None | Ordered list of feed sources; earlier entries get higher materiality weight. |
| emit_every_above | 0.7 | 0.5 | 0.4 | Materiality 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
| Condition | Action |
|---|---|
| score ≥ 0.3 | Emit ObservationReport |
| 0.1–0.3 | WARN — borderline; include with LOW_MATERIALITY flag |
| < 0.05 | Hard 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
| Condition | Action |
|---|---|
| window ≥ 300 s | Standard dedup |
| 60–300 s | WARN — tighter window; risk of some duplicates |
| < 10 s | Reject 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
| Condition | Action |
|---|---|
| score ≥ 0.7 | emit-every — no sampling |
| 0.3–0.7 | sample-1/10 |
| < 0.4 | Cannot 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
- Subscribe to all configured external feeds (RSS polling + partner API long-poll or webhook).
- On each incoming story: compute a 64-bit fingerprint (source + headline hash). Check dedup_window_s ring buffer; if duplicate, drop silently.
- Perform entity extraction (NER); resolve entities against Gamma API market metadata to produce a list of candidate condition_ids.
- If no condition_id matched, drop story with NEWSINGEST_NO_MARKET_MATCH.
- Score materiality (0–1) based on: source priority weight × entity confidence × recency decay × neg-risk amplifier if any matched market has neg_risk=true.
- If score < materiality_min hard floor, drop with NEWSINGEST_BELOW_MATERIALITY_FLOOR.
- Check KillSwitch; if active, ingest and score but suppress ObservationReport emissions.
- Apply sampling rule: if score ≥ emit_every_above, emit every time; else sample 1/10.
- Emit ObservationReport with: report_id, trace_id, condition_ids, materiality_score, story_summary, source, pub_ts_ms, neg_risk_flag, and sampling_applied.
- 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
| Code | Severity | Meaning | Action | User-facing message |
|---|---|---|---|---|
NEWSINGEST_DEDUP_DROP | INFO | Story fingerprint matched a recent entry in the dedup ring buffer. | Silently drop; log at DEBUG level. | |
NEWSINGEST_NO_MARKET_MATCH | INFO | Entity extraction produced no condition_ids from Gamma API. | Drop story; no ObservationReport emitted. | |
NEWSINGEST_BELOW_MATERIALITY_FLOOR | INFO | Story materiality score below hard floor; irrelevant to any open position. | Drop story silently. | |
NEWSINGEST_LOW_MATERIALITY | WARN | Story score between warning and default thresholds; forwarded with flag. | Emit ObservationReport with LOW_MATERIALITY warning; downstream bots decide. | |
KILL_SWITCH_ACTIVE | HARD_REJECT | KillSwitch is active; ObservationReport emissions suppressed. | Continue ingesting and scoring but do not emit. | News signals are paused while trading is suspended system-wide. |
STALE_DATA | WARN | Gamma API unavailable; entity resolution halted for this cycle. | Buffer incoming stories up to dedup_window_s; halt emissions until API recovers. | |
MARKET_CLOSED | EXPLAIN | Entity resolved to a market that is already closed or resolved. | Skip that condition_id; continue processing remaining matched markets. | |
NEWSINGEST_FEED_UNREACHABLE | WARN | An external feed (RSS / partner API) is unreachable. | Log WARN; continue processing other feeds; alert if all feeds unreachable. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|---|---|---|---|
polytraders_intel_newsingest_stories_received_total | counter | count | source | Total raw stories received from all feeds, per source. |
polytraders_intel_newsingest_observations_emitted_total | counter | count | source, sampling_applied | ObservationReport items emitted to the internal bus. |
polytraders_intel_newsingest_drop_total | counter | count | reason_code | Stories dropped, broken down by drop reason code. |
polytraders_intel_newsingest_materiality_score | histogram | ratio | Distribution of materiality scores for all stories that passed entity resolution. | |
polytraders_intel_newsingest_entity_resolution_latency_ms | histogram | ms | Wall-clock time for Gamma API entity resolution per story. | |
polytraders_intel_newsingest_feed_lag_s | gauge | seconds | source | Age of the most recent story received from each feed source. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|---|---|---|
NewsIngestAllFeedsDown | rate(polytraders_intel_newsingest_stories_received_total[5m]) == 0 | page | #runbook-newsingest-feeds-down |
NewsIngestGammaAPIDown | rate(polytraders_intel_newsingest_entity_resolution_latency_ms_count[5m]) == 0 AND rate(polytraders_intel_newsingest_stories_received_total[5m]) > 0 | page | #runbook-newsingest-gamma-api-down |
NewsIngestHighDropRate | rate(polytraders_intel_newsingest_drop_total[10m]{reason_code='NEWSINGEST_NO_MARKET_MATCH'}) / rate(polytraders_intel_newsingest_stories_received_total[10m]) > 0.95 | warn | #runbook-newsingest-entity-mismatch |
NewsIngestFeedLag | polytraders_intel_newsingest_feed_lag_s{source='reuters'} > 120 | warn | #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
| Situation | User-facing explanation |
|---|---|
| Fewer news signals than expected | Many 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 time | Stories 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 KillSwitch | News ingestion continues in the background, but no signals are forwarded while trading is paused system-wide. |
18. Failure-Mode Block
| main_failure_mode | Entity 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_risk | A 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_risk | A high-materiality story is dropped due to an entity resolution miss — the story mentions an entity in shorthand not in the watchlist dictionary. |
| safe_fallback | If 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_dependencies | External feeds (RSS / partner APIs) reachable, Gamma API for entity-to-market resolution, KillSwitch active flag readable |
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|---|---|---|
STALE_FEED | Disconnect Reuters RSS for 120 s | Automatic when feed reconnects within dedup_window_s; buffered stories re-processed | |
GAMMA_API_DOWN | Block TCP to gamma-api.polymarket.com | Automatic on Gamma API recovery; buffered stories re-processed | |
HIGH_VOLUME_FLOOD | Inject 1 000 unique stories/s for 10 s | Automatic when ingestion rate drops below queue drain rate | |
KILL_SWITCH_ON | Set killswitch.active=true | Emissions resume on first event after KillSwitch reset | |
ENTITY_RESOLUTION_MISS | Submit story mentioning a real market entity not in the watchlist dictionary | Add 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
| Aspect | Specification |
|---|---|
| Execution model | single-threaded event loop |
| Max in-flight | 10 |
| Idempotency key | story fingerprint |
| Per-call timeout (ms) | 3000 |
| Backpressure strategy | drop-after-buffer — excess stories dropped when internal queue > 500 items |
| Locking / mutual exclusion | none — dedup ring buffer access is single-threaded |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|---|---|
| risk.kill_switch | KillSwitch gate determines whether ObservationReports are emitted. |
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|---|---|
| strat.news_materiality_trader | ||
strat.contradiction_detector |
External services
| Service | Endpoint | SLA assumed | On failure |
|---|---|---|---|
| Reuters RSS / AP wire | best-effort; typically < 5 s from event to wire | ||
| Bloomberg headline API | 99.5% / 1 s p99 | ||
| Gamma API | https://gamma-api.polymarket.com | 99.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
| Aspect | Value |
|---|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | no |
| Aware of negative-risk markets | yes |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | NewsIngest 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
Networks supported
25. Versioning & Migration
| Field | Value |
|---|---|
| spec | 2.0.0 |
| implementation | 2.1.0 |
| schema | 2 |
| released | 2026-04-28 |
Migration history
| Date | From | To | Reason | Action taken |
|---|---|---|---|---|
| 2026-04-28 | v1 | v2 | CLOB V2 cutover — collateral denomination change | Materiality 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
| Test | Setup | Expected result |
|---|---|---|
| Duplicate story is dropped within dedup_window_s | Inject same headline twice within 60 s | Second occurrence silently dropped; NEWSINGEST_DEDUP_DROP logged |
| Story below materiality hard floor is dropped | story.score=0.03, hard=0.05 | Story dropped with NEWSINGEST_BELOW_MATERIALITY_FLOOR |
| High-materiality story emitted without sampling | story.score=0.82 ≥ emit_every_above=0.7 | ObservationReport emitted with sampling_applied=false |
| Routine story sampled 1/10 | story.score=0.4, emit_every_above=0.7; run 10 stories | Approximately 1 ObservationReport emitted; sampling_applied=true on remainder |
| KillSwitch suppresses emissions | killswitch.active=true; high-materiality story arrives | Story scored but no ObservationReport emitted; KILL_SWITCH_ACTIVE logged |
| No market match drops story | Story entities not in Gamma API watchlist | Drop with NEWSINGEST_NO_MARKET_MATCH |
Integration Tests
| Test | Expected result |
|---|---|
| AP wire story reaches contradictiondetector and news-materiality-trader | ObservationReport with correct condition_id propagates to both downstream bots within 2 s |
| Gamma API unavailability halts emission with buffering | No ObservationReports emitted during outage; stories buffered; re-processed on recovery |
| neg-risk market amplifies materiality score | Story matched to neg-risk market has materiality_score elevated and neg_risk_flag=true in output |
Property Tests
| Property | Required behaviour |
|---|---|
| NewsIngest never submits, signs, or modifies any order | Always true |
| No ObservationReport emitted when KillSwitch is active | Always true |
| No ObservationReport emitted when Gamma API is unavailable | Always 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
| Alert | First step | Diagnosis | Mitigation | Escalate 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
| Gate | How measured | Threshold |
|---|---|---|
| Unit tests pass for dedup, materiality scoring, and KillSwitch suppression | CI test run | 100% pass |
| Gamma API entity resolution integration test: known entity resolves to correct condition_id | Integration test | Pass |
Promote to Limited live
| Gate | How measured | Threshold |
|---|---|---|
| Entity resolution latency p99 < 500 ms over 24 h | polytraders_intel_newsingest_entity_resolution_latency_ms histogram | p99 < 500 ms |
| neg-risk materiality amplifier produces correct neg_risk_flag in output | Integration test with known neg-risk market entity | Pass |
Promote to General live
| Gate | How measured | Threshold |
|---|---|---|
| Zero false NEWSINGEST_NO_MARKET_MATCH for entities in the curated watchlist over 7 days | Grafana drop_total metric filtered by reason_code | < 1% miss rate on watchlist entities |
| KillSwitch suppression verified: zero ObservationReports emitted when KillSwitch active | Integration test | Pass |
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|---|
| 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 |