1. Bot Identity
| Layer | Governance Governance |
|---|
| Bot class | Governance Service |
|---|
| Authority | Explain |
|---|
| Status | LIVE |
|---|
| Readiness | General live |
|---|
| Runs before | PnLReporter (provides position state), PortfolioGuard (provides current exposure for risk checks) |
|---|
| Runs after | Every fill event; on configured sync_interval_s; on-demand after significant position change |
|---|
| Applies to | All open positions and open orders across all active markets, including negative-risk multi-outcome positions |
|---|
| Default mode | general_live |
|---|
| User-visible | Summary only |
|---|
| Developer owner | Polytraders core — Governance pod |
|---|
2. Purpose
PortfolioSync keeps the in-memory portfolio state — open positions and open orders — continuously synchronised with on-chain pUSD balances and CLOB open-order state. It reconciles the internal position store against clob_auth open orders and the Polygon on-chain balance every sync_interval_s. For negative-risk markets (NegRiskAdapter), it correctly aggregates multi-outcome positions using the negRisk flag. On discrepancy beyond discrepancy_alert_usd, it emits an alert and, if configured, pauses the affected strategy to prevent trading against a stale view of risk. Emits both a SettlementReport (position deltas) and an OperationsReport (sync health) on every cycle.
3. Why This Bot Matters
In-memory position state drifts from on-chain reality
Strategies trade against a stale position view. Risk guards (PortfolioGuard, KillSwitch) see incorrect exposure, potentially allowing positions that exceed risk limits.
Negative-risk positions aggregated incorrectly
Multi-outcome positions in negRisk markets have correlated payoffs. Treating them as independent binary positions overstates available margin and may cause liquidation.
Open-order count diverges from CLOB
Strategies submit orders assuming capacity that no longer exists. This inflates fill expectations and distorts P&L projections.
Strategy not paused on drift beyond threshold
Trading continues against a stale portfolio state. Risk exposure is uncontrolled until the next successful sync cycle.
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.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| sync_interval_s | 30 | 120 | 300 | How often PortfolioSync reconciles the in-memory state against CLOB and on-chain sources. |
| discrepancy_alert_usd | 10 | 50 | 500 | Threshold in pUSD above which a position discrepancy triggers an alert (and optionally pauses the affected strategy). |
| auto_pause_strategy_on_drift | True | None | None | When true, automatically pauses the affected strategy when drift exceeds discrepancy_alert_usd hard threshold. |
7. Detailed Parameter Instructions
sync_interval_s
What it means
How often PortfolioSync reconciles the in-memory state against CLOB and on-chain sources.
Default
{ "sync_interval_s": 30 }
Why this default matters
30s gives a 1-minute worst-case drift window. Longer intervals risk strategies trading against significantly stale positions.
Threshold logic
| Condition | Action |
|---|
| sync_interval_s <= 30 | Normal sync cadence |
| 30–120s | WARN — drift window increased |
| > 300s | Reject — PARAMETER_CHANGE_REQUIRES_APPROVAL |
Developer check
if (p.sync_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')
User-facing English
Your portfolio state is updated regularly to stay in sync with the exchange.
discrepancy_alert_usd
What it means
Threshold in pUSD above which a position discrepancy triggers an alert (and optionally pauses the affected strategy).
Default
{ "discrepancy_alert_usd": 10 }
Why this default matters
10 pUSD catches even small drift from fill-rounding or missed cancellation events. 500 pUSD is a hard cap — drift above this is always critical.
Threshold logic
| Condition | Action |
|---|
| drift <= 10 pUSD | Log INFO; no alert |
| 10–500 pUSD | Emit WARN PORTFOLIO_SYNC_DISCREPANCY |
| > 500 pUSD | Emit page PORTFOLIO_SYNC_CRITICAL_DISCREPANCY; auto-pause strategy |
Developer check
if (drift > p.hard) alerting.emit('PORTFOLIO_SYNC_CRITICAL_DISCREPANCY', { drift_pusd: drift })
User-facing English
The system alerts the team if there is any significant mismatch between the internal record and the actual position.
auto_pause_strategy_on_drift
What it means
When true, automatically pauses the affected strategy when drift exceeds discrepancy_alert_usd hard threshold.
Default
{ "auto_pause_strategy_on_drift": true }
Why this default matters
True by default — trading against a stale position view is a safety risk. Auto-pause provides a safe floor without requiring manual intervention.
Threshold logic
| Condition | Action |
|---|
| auto_pause_strategy_on_drift=true AND drift > hard | Pause affected strategy; emit PORTFOLIO_SYNC_STRATEGY_PAUSED |
| auto_pause_strategy_on_drift=false AND drift > hard | Emit page alert only; strategy continues (operator assumes responsibility) |
Developer check
if (p.auto_pause_strategy_on_drift && drift > p.discrepancy_alert_usd.hard) pauseStrategy(affected_strategy_id)
User-facing English
If the portfolio state cannot be verified, affected trading strategies are paused automatically as a precaution.
8. Default Configuration
{
"bot_id": "gov.portfolio_sync",
"version": "2.0.0",
"mode": "general_live",
"defaults": {
"sync_interval_s": 30,
"discrepancy_alert_usd": 10,
"auto_pause_strategy_on_drift": true
},
"locked": {
"sync_interval_s": {
"max": 300
},
"discrepancy_alert_usd": {
"max": 500
}
}
}
9. Implementation Flow
- Every sync_interval_s, snapshot the internal in-memory position store (all open positions and open orders).
- Fetch open orders from clob_auth for the wallet address; fetch on-chain pUSD balance from Polygon RPC.
- For each open position in the internal store: fetch the corresponding CLOB position and compare size, side, and average cost.
- For negative-risk markets (negRisk=true): use the NegRiskAdapter aggregation to compute the combined multi-outcome position before comparing.
- Compute drift_pusd = abs(internal_position_pusd - clob_position_pusd) for each market.
- If drift_pusd > discrepancy_alert_usd: emit WARN PORTFOLIO_SYNC_DISCREPANCY; if drift_pusd > discrepancy_alert_usd.hard, emit page and optionally pause strategy (auto_pause_strategy_on_drift).
- Apply deltas to the internal position store: add missing positions, remove closed positions, update sizes.
- Emit SettlementReport for each position delta (position added, removed, or updated).
- Emit OperationsReport for the full sync cycle: total_positions, synced_count, discrepancy_count, drift_pusd_max, sync_duration_ms.
- Retain SettlementReports for 7 years (position deltas are regulatory records). OperationsReports retained 1 year.
10. Reference Implementation
Every sync_interval_s: snapshots internal state, fetches CLOB open orders and on-chain pUSD balance, reconciles per-market positions (with negRisk aggregation), applies deltas, emits SettlementReport per delta and OperationsReport per cycle.
Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.
// ---- SYNC LOOP ----
FUNCTION runSync():
sync_start = now()
internal_snapshot = position_store.snapshot() // all open positions
// Fetch external state
clob_orders = FETCH clob_auth.GET('/orders?wallet=' + wallet_address)
ON_FAILURE: EMIT OperationsReport(event_type='PORTFOLIO_SYNC_CLOB_UNAVAILABLE'); RETURN
onchain_balance = FETCH onchain.getBalance(wallet_address, 'pUSD')
ON_FAILURE: EMIT OperationsReport(event_type='PORTFOLIO_SYNC_RPC_UNAVAILABLE'); RETURN
// Reconcile per-market
discrepancy_count = 0; drift_max = 0; deltas = []
FOR market_id IN union(keys(internal_snapshot), keys(clob_orders)):
internal_pos = internal_snapshot.get(market_id, ZERO)
clob_pos = clob_orders.get(market_id, ZERO)
market_meta = FETCH clob_auth.getMarketByConditionId(market_id)
IF market_meta.negRisk:
// Aggregate all outcome tokens via NegRiskAdapter
clob_pos = negRiskAggregate(clob_orders, market_id)
internal_pos = negRiskAggregate(internal_snapshot, market_id)
drift_pusd = abs(internal_pos.size_pusd - clob_pos.size_pusd)
drift_max = max(drift_max, drift_pusd)
IF drift_pusd > config.discrepancy_alert_usd:
discrepancy_count += 1
alerting.emit('PORTFOLIO_SYNC_DISCREPANCY', { market_id, drift_pusd })
IF drift_pusd > config.discrepancy_alert_usd.hard: // 500 pUSD
alerting.emit('PORTFOLIO_SYNC_CRITICAL_DISCREPANCY', { market_id, drift_pusd })
IF config.auto_pause_strategy_on_drift:
pauseStrategy(getStrategyForMarket(market_id))
EMIT SettlementReport(event_type='PORTFOLIO_SYNC_STRATEGY_PAUSED', ...)
delta_type = computeDeltaType(internal_pos, clob_pos)
IF delta_type != 'NO_CHANGE':
position_store.apply(market_id, clob_pos)
deltas.append({ market_id, delta_type, drift_pusd, negRisk: market_meta.negRisk })
EMIT SettlementReport(event_type='POSITION_DELTA', market_id=market_id, ...)
// Ops report for this cycle
EMIT OperationsReport({
event_type: 'SYNC_CYCLE_COMPLETE',
total_positions: len(union(internal_snapshot, clob_orders)),
synced_count: len(deltas),
discrepancy_count: discrepancy_count,
drift_pusd_max: drift_max,
onchain_balance: onchain_balance,
sync_duration_ms: now() - sync_start
})
SDK calls used
clob_auth.GET('/orders?wallet=<address>')clob_auth.getMarketByConditionId(market_id)onchain.getBalance(wallet_address, 'pUSD')negRiskAggregate(orders, market_id)alerting.emit('PORTFOLIO_SYNC_DISCREPANCY', { market_id, drift_pusd })pauseStrategy(strategy_id)
Complexity: O(M) per sync cycle where M = number of open markets
11. Wire Examples
Input — what arrives on the wire
{
"label": "CLOB open orders response",
"source": "clob_auth",
"payload": {
"wallet": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"open_orders": [
{
"order_id": "ord_00456",
"market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"side": "BUY",
"size_pusd": 1250.0,
"price": 0.6,
"negRisk": false,
"timestamp": 1746791900000
}
],
"onchain_balance_pusd": 52341.0
}
}
Output — what the bot emits
{
"label": "SettlementReport — POSITION_DELTA",
"payload": {
"report_id": "settlement_portfoliosync_1746792000000",
"bot_id": "gov.portfolio_sync",
"event_type": "POSITION_DELTA",
"market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"delta_type": "UPDATED",
"internal_size_pusd": 1200.0,
"clob_size_pusd": 1250.0,
"drift_pusd": 50.0,
"negRisk": false,
"action_taken": "internal_store_updated",
"synced_at_ms": 1746792000000,
"report_kind": "SettlementReport",
"retained_until": "2033-05-09"
}
}
12. Decision Logic
APPROVE
Not applicable — PortfolioSync does not approve or reject trading decisions.
RESHAPE_REQUIRED
Not applicable.
REJECT
Not applicable as a trading decision. PortfolioSync may pause a strategy if drift exceeds the hard threshold and auto_pause_strategy_on_drift=true.
WARNING_ONLY
Drift below the hard threshold emits a WARN but does not pause any strategy. Stale price or CLOB connectivity issues emit WARN STALE_DATA.
13. Standard Decision Output
This bot returns a SettlementReport object. See SettlementReport schema.
{
"report_id": "settlement_portfoliosync_1746792000000",
"bot_id": "gov.portfolio_sync",
"event_type": "POSITION_DELTA",
"market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"delta_type": "UPDATED",
"internal_size_pusd": 1200.0,
"clob_size_pusd": 1250.0,
"drift_pusd": 50.0,
"negRisk": false,
"action_taken": "internal_store_updated",
"synced_at_ms": 1746792000000,
"report_kind": "SettlementReport"
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
PORTFOLIO_SYNC_CYCLE_COMPLETE | INFO | Full sync cycle completed; OperationsReport emitted. | No action — routine. | |
PORTFOLIO_SYNC_DISCREPANCY | WARN | Position drift between internal store and CLOB exceeds discrepancy_alert_usd threshold. | Emit alert; apply delta to internal store; log drift_pusd. | A small difference was found between the internal record and the exchange. It was corrected. |
PORTFOLIO_SYNC_CRITICAL_DISCREPANCY | WARN | Position drift exceeds the hard threshold (500 pUSD). This indicates a significant state divergence. | Emit page alert; optionally pause affected strategy (auto_pause_strategy_on_drift). | A significant difference was found. The affected strategy has been paused while the team investigates. |
PORTFOLIO_SYNC_STRATEGY_PAUSED | WARN | A strategy was automatically paused because position drift exceeded the hard threshold. | Notify on-call; do not resume until drift is investigated and resolved. | A trading strategy was paused as a precaution while portfolio state is being verified. |
PORTFOLIO_SYNC_CLOB_UNAVAILABLE | WARN | CLOB auth API was unavailable during a sync cycle; no delta applied. | Skip cycle; emit alert; retry on next interval. | |
PORTFOLIO_SYNC_NEGRISK_AGGREGATE | INFO | A negative-risk multi-outcome market was encountered; NegRiskAdapter aggregation applied. | No action — informational. | |
STALE_DATA | WARN | CLOB or RPC data is older than sync_interval_s; internal store may be stale. | Emit WARN; skip delta application; retry sync on next cycle. | Portfolio synchronisation is delayed. The team has been notified. |
PARAMETER_CHANGE_REQUIRES_APPROVAL | HARD_REJECT | sync_interval_s exceeds the 300s hard maximum. | Reject the config change; emit alert. | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_gov_portfoliosync_positions_synced_total | counter | count | delta_type | Total position deltas applied (ADDED, REMOVED, UPDATED) across all sync cycles. |
polytraders_gov_portfoliosync_discrepancy_count | gauge | count | | Number of markets with drift above discrepancy_alert_usd at the last sync cycle. |
polytraders_gov_portfoliosync_drift_pusd_max | gauge | usd | | Maximum per-market drift (pUSD) observed in the last sync cycle. |
polytraders_gov_portfoliosync_sync_duration_ms | histogram | ms | | Wall-clock duration of a full sync cycle. |
polytraders_gov_portfoliosync_strategies_paused_total | counter | count | strategy_id | Total strategy pause events triggered by critical position drift. |
polytraders_gov_portfoliosync_sync_cycles_total | counter | count | status | Total sync cycles completed, labelled by status (ok, clob_unavailable, rpc_unavailable). |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
PortfolioSyncCriticalDiscrepancy | polytraders_gov_portfoliosync_drift_pusd_max > 500 | page | #runbook-portfoliosync-critical-discrepancy |
PortfolioSyncStrategyPaused | rate(polytraders_gov_portfoliosync_strategies_paused_total[5m]) > 0 | page | #runbook-portfoliosync-strategy-paused |
PortfolioSyncClobUnavailable | rate(polytraders_gov_portfoliosync_sync_cycles_total{status='clob_unavailable'}[10m]) > 0 | warn | #runbook-portfoliosync-clob-unavailable |
PortfolioSyncNoSyncIn5m | rate(polytraders_gov_portfoliosync_sync_cycles_total[5m]) == 0 | page | #runbook-portfoliosync-no-sync |
Dashboards
- Grafana — Governance / PortfolioSync position drift timeline
- Grafana — Governance / PortfolioSync negRisk position aggregation
16. Developer Reporting
{
"bot_id": "gov.portfolio_sync",
"event_type": "SYNC_CYCLE_COMPLETE",
"total_positions": 12,
"synced_count": 11,
"discrepancy_count": 1,
"drift_pusd_max": 50.0,
"negRisk_positions": 2,
"paused_strategies": [],
"sync_duration_ms": 420,
"synced_at_ms": 1746792000000
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Sync cycle completed with no discrepancies | Your portfolio state matches the exchange records exactly. |
| Minor discrepancy detected and corrected | A small difference was found between the internal record and the exchange. It was corrected automatically. |
| Strategy paused due to portfolio drift | A significant difference was found between the internal position record and the exchange. The affected strategy has been paused while the team investigates. |
| Multi-outcome market position in portfolio | Some positions are in multi-outcome markets. These are tracked and valued using the correct multi-outcome formula. |
18. Failure-Mode Block
| main_failure_mode | CLOB auth API or Polygon RPC is unavailable during a sync cycle. The internal position store cannot be reconciled; strategies may trade against stale state. |
|---|
| false_positive_risk | Transient CLOB API latency causes a fill-in-progress to appear as a position discrepancy, triggering a spurious WARN that resolves on the next sync cycle. |
|---|
| false_negative_risk | A negRisk position is not correctly identified (negRisk flag missing on market metadata), leading to under-aggregation of multi-outcome exposure and a silent undercount of position size. |
|---|
| safe_fallback | If CLOB API or RPC is unavailable, skip the sync cycle, emit WARN, and retry on the next scheduled interval. Do not apply partial deltas. Emit PORTFOLIO_SYNC_CLOB_UNAVAILABLE. If drift exceeds hard threshold from a stale state, emit the page alert regardless. |
|---|
| required_dependencies | CLOB auth API (open orders per wallet), Polygon on-chain RPC (pUSD balance), Internal in-memory position store, Market metadata API (negRisk flag) |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
CLOB_API_UNAVAILABLE | Block TCP to clob.polymarket.com during a sync cycle | PORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; no delta applied; internal state unchanged; retry on next interval | CLOB reconnects; next cycle performs full sync. |
CRITICAL_DRIFT | Manually edit the in-memory position store to show 600 pUSD more than CLOB | PORTFOLIO_SYNC_CRITICAL_DISCREPANCY page alert; affected strategy paused (auto_pause_strategy_on_drift=true) | Investigate root cause; resume strategy via manual override after verification. |
NEGRISK_MISCONFIGURED | Set negRisk=false on a multi-outcome market; submit 3 outcome token positions | Positions reconciled as independent binary positions; drift may be flagged if NegRisk aggregation would give different total | Update market metadata with correct negRisk flag; re-sync. |
ONCHAIN_RPC_FAILURE | Block Polygon RPC calls during sync | PORTFOLIO_SYNC_RPC_UNAVAILABLE in OperationsReport; on-chain balance check skipped; position deltas still applied from CLOB data | RPC reconnects; on-chain balance check resumes on next cycle. |
SYNC_LOOP_STALL | Introduce 10s artificial delay in CLOB fetch (beyond timeout_ms=5000) | CLOB fetch times out; PORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; cycle skipped | CLOB latency normalises; next cycle completes within timeout. |
20. State & Persistence
Cold-start recovery
On restart, position_store is rebuilt from a full CLOB sync on startup. First sync cycle restores complete state before any trading is allowed.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | single-threaded event loop (sync cycle) + async CLOB/RPC fetch |
| Max in-flight | 50 |
| Idempotency key | market_id + synced_at_ms |
| Per-call timeout (ms) | 5000 |
| Backpressure strategy | if CLOB fetch exceeds timeout, skip cycle and emit PORTFOLIO_SYNC_CLOB_UNAVAILABLE |
| Locking / mutual exclusion | per-market RwLock on position_store entries |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|
internal.clob_auth | Open orders and market metadata are fetched from clob_auth on each sync cycle. | |
internal.onchain_rpc | pUSD on-chain balance cross-check requires Polygon RPC access. | |
Emits to (downstream consumers)
Sibling bots (same OrderIntent)
| Bot | Why | Contract |
|---|
| gov.pnl_reporter | PortfolioSync provides the position state that PnLReporter uses for unrealised P&L computation. | |
Used by (auto-aggregated)
6.3
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Polymarket CLOB v2 (open orders, market metadata) | | 99.95% / 200ms p99 (Polymarket-published) | |
| Polygon on-chain RPC (pUSD balance) | | 99.9% / 500ms p99 | |
23. Security Surfaces
Abuse vectors considered
- Raising discrepancy_alert_usd hard threshold to suppress critical drift alerts
- Disabling auto_pause_strategy_on_drift to allow trading against stale state
- Manipulating the CLOB open-order response to show smaller positions than actual
Mitigations
- discrepancy_alert_usd has a hard maximum of 500 pUSD enforced at config load
- sync_interval_s has a hard maximum of 300s enforced at config load
- On-chain balance provides an independent cross-check that cannot be manipulated via CLOB API
- PortfolioSync never signs orders or holds private keys — read-only access to CLOB and RPC
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 on Polygon |
| Notes | PortfolioSync correctly handles negative-risk multi-outcome positions via NegRiskAdapter aggregation. All position sizes and pUSD balances are denominated in pUSD (not USDC.e). Open orders fetched via clob_auth V2 endpoint. |
API surfaces declared
clob_authonchaininternal
Networks supported
polygon
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 (USDC.e denomination, V1 CLOB open-order endpoint) | v2 (pUSD denomination, clob_auth V2 endpoint) | CLOB V2 cutover | Updated all position sizes from USDC.e to pUSD denomination. Switched to clob_auth V2 open-order endpoint (py-clob-client-v2). Added NegRiskAdapter aggregation path for negative-risk multi-outcome markets. Updated SettlementReport schema to include negRisk flag per delta. |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| Discrepancy alert fires above discrepancy_alert_usd | internal_size=1000 pUSD, clob_size=1060 pUSD (drift=60) | PORTFOLIO_SYNC_DISCREPANCY emitted; internal store updated |
| Strategy paused at hard threshold | drift_pusd=600 > hard=500; auto_pause_strategy_on_drift=true | PORTFOLIO_SYNC_CRITICAL_DISCREPANCY alert; strategy paused; PORTFOLIO_SYNC_STRATEGY_PAUSED emitted |
| negRisk position aggregated correctly | 3 outcome tokens in same negRisk market; sizes [400, 300, 200] pUSD | Combined position = NegRiskAdapter aggregate; not sum of three binary positions |
| sync_interval_s above hard maximum rejected | sync_interval_s=400 | ConfigError PARAMETER_CHANGE_REQUIRES_APPROVAL |
| No delta applied on partial CLOB API response | CLOB returns partial response (missing 3 positions) | PORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; no partial delta applied; internal store unchanged |
Integration Tests
| Test | Expected result |
|---|
| End-to-end sync cycle: internal store matches CLOB after reconciliation | SettlementReport per delta + OperationsReport per cycle emitted; Postgres updated |
| On-chain balance cross-check passes after fill | pUSD balance within reconcile tolerance; SettlementReport onchain_reconciled=true |
Property Tests
| Property | Required behaviour |
|---|
| Internal position store is never more stale than sync_interval_s + sync_duration | Always true — partial sync is never applied |
| Every position delta emits exactly one SettlementReport | Always true |
27. Operational Runbook
PortfolioSync incidents fall into: critical position drift (strategy auto-paused), CLOB/RPC unavailability (sync skipped), or negRisk misconfiguration (silent exposure undercount). Drift above 500 pUSD is always P1.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
PortfolioSyncCriticalDiscrepancy | Identify which market(s) have critical drift. Check CLOB and on-chain balance directly. Do NOT resume strategy until root cause is confirmed. | | | Governance pod lead immediately |
PortfolioSyncStrategyPaused | Review PORTFOLIO_SYNC_CRITICAL_DISCREPANCY logs. Confirm which strategy was paused and why. | | | Governance pod lead + strategy owner |
PortfolioSyncClobUnavailable | Check Polymarket CLOB API status. If unavailable, monitor sync recovery. Strategies are not paused but are trading against stale state. | | | SRE on-call if CLOB unavailability exceeds 5 minutes |
PortfolioSyncNoSyncIn5m | Check PortfolioSync process status and internal bus connectivity. | | | Governance pod lead |
Manual overrides
polytraders gov portfolio force-sync — Trigger an immediate full sync cycle outside the normal interval.polytraders gov portfolio resume-strategy --strategy-id <id> --reviewed-by <operator> — Resume an auto-paused strategy after drift root cause has been investigated and resolved.
Healthcheck
Endpoint: /internal/health/portfolio-sync | Green: Last sync cycle completed within 2x sync_interval_s; CLOB reachable; discrepancy_count = 0; no strategies paused. | Red: No sync in 2x sync_interval_s; CLOB unavailable > 5 min; critical drift detected; strategies paused.
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 |