1. Bot Identity
| Layer | Governance Governance |
|---|
| Bot class | Governance Service |
|---|
| Authority | Explain |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Nothing — runs post-fill on every ExecutionReport event |
|---|
| Runs after | Order fill confirmation from CTFExchangeV2 |
|---|
| Applies to | Every filled or partially filled order |
|---|
| Default mode | shadow_only |
|---|
| User-visible | summary-only |
|---|
| Developer owner | Polytraders core |
|---|
2. Purpose
PostTradeExplainer turns every fill into a plain-English SettlementReport: which strategy, which signal, which guard checks passed, why this size, why this price. Retained 7 years for regulatory compliance.
3. Why This Bot Matters
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 |
|---|
| retain_explanation_days | 2555 | 1825 | 2555 | Retention period in days for SettlementReport records (default 7 years = 2555 days). |
| min_detail_level | standard | None | None | Minimum level of detail in the explanation (standard / advanced). |
7. Detailed Parameter Instructions
retain_explanation_days
What it means
Retention period in days for SettlementReport records (default 7 years = 2555 days).
Default
{ "retain_explanation_days": 2555 }
Why this default matters
7-year retention is required by financial regulations.
Threshold logic
| Condition | Action |
|---|
| retain_explanation_days < 2555 | WARN — below regulatory minimum |
Developer check
if p.retain_explanation_days < 2555: emit('RETENTION_BELOW_REGULATORY_MINIMUM')
User-facing English
Your trade explanations are kept on record for 7 years as required by regulation.
min_detail_level
What it means
Minimum level of detail in the explanation (standard / advanced).
Default
{ "min_detail_level": "standard" }
Why this default matters
Standard includes strategy name, signal summary, and guard votes. Advanced adds raw parameter values.
Threshold logic
| Condition | Action |
|---|
| min_detail_level=standard | Include: strategy, signal summary, guard votes, size rationale, fill price |
Developer check
if p.min_detail_level == 'advanced': explanation.include(raw_params=True)
User-facing English
Each trade record includes the reason it was made.
8. Default Configuration
{
"bot_id": "gov.posttradeexplainer",
"version": "0.1.0",
"mode": "shadow_only",
"defaults": {
"retain_explanation_days": 2555,
"min_detail_level": "standard",
"publish_to_user": true,
"require_for_advanced": false
}
}
9. Implementation Flow
- Subscribe to ExecutionReport events on the internal report bus.
- For each ExecutionReport, fetch the linked DecisionReport and RiskVote from the report archive.
- Compose the explanation: strategy name, signal summary, guard vote outcomes, size rationale, fill price, builder_fee_pusd.
- Generate a plain-English summary sentence for user display.
- Emit SettlementReport(event_type=TRADE_EXPLAINED) with full explanation and plain-English summary.
- Store SettlementReport in the post-trade archive with retain_explanation_days retention.
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.
// ---- SUBSCRIBE TO EXECUTION REPORTS ----
FUNCTION onExecutionReport(execReport):
// Fetch supporting records from archive
decisionReport = FETCH internal.reportArchive.GET({
trace_id: execReport.trace_id, kind: 'DecisionReport'
})
riskVotes = FETCH internal.reportArchive.GET({
trace_id: execReport.trace_id, kind: 'RiskVote'
})
complete = decisionReport IS NOT NULL AND riskVotes IS NOT NULL
// Compose explanation
explanation = {
fill_id: execReport.fill_id,
strategy: decisionReport?.strategy_slug ?? 'unknown',
signal_summary: decisionReport?.signal_summary ?? '',
guard_votes: riskVotes ?? [],
size_rationale: buildSizeRationale(decisionReport, riskVotes),
fill_price: execReport.fill_price,
fill_size_pusd: execReport.fill_size_pusd,
builder_fee_pusd: execReport.builder_fee_pusd,
plain_english: composePlainEnglish(explanation),
explanation_complete: complete
}
// Emit SettlementReport
EMIT SettlementReport(event_type='TRADE_EXPLAINED',
...explanation,
report_kind='SettlementReport',
topic='polytraders.reports.settlement',
retained_until=now() + days(config.retain_explanation_days))
FUNCTION composePlainEnglish(exp):
side = 'Bought' IF exp.fill_side == 'BUY' ELSE 'Sold'
return f"{side} {exp.fill_size_pusd} pUSD at {exp.fill_price} — {exp.signal_summary}; all risk checks passed."
SDK calls used
internal.reportArchive.GET({trace_id, kind})alerting.emit('EXPLANATION_ARCHIVE_UNAVAILABLE', metadata)
Complexity: O(1) per fill; O(R) for archive fetch where R = linked report count
11. Wire Examples
Input — what arrives on the wire
{
"label": "ExecutionReport from fill",
"source": "internal.report_bus",
"payload": {
"fill_id": "fill_00a1b2c3d4e5f6a7",
"trace_id": "trc_01HX9Z",
"fill_price": 0.621,
"fill_size_pusd": 430.0,
"builder_fee_pusd": 1.075,
"fill_confirmed_at_ms": 1746792060000
}
}
Output — what the bot emits
{
"label": "SettlementReport — TRADE_EXPLAINED",
"payload": {
"report_id": "stl_01HX9Z",
"event_type": "TRADE_EXPLAINED",
"fill_id": "fill_00a1b2c3d4e5f6a7",
"plain_english": "Bought 430 pUSD of YES at 0.621 — sports model edge 4.2 bps; all risk checks passed.",
"report_kind": "SettlementReport",
"topic": "polytraders.reports.settlement",
"retained_until": "2033-05-09"
}
}
12. Decision Logic
APPROVE
Not applicable — PostTradeExplainer does not approve or reject trades.
RESHAPE_REQUIRED
Not applicable.
WARNING_ONLY
Emits RETENTION_BELOW_REGULATORY_MINIMUM if retain_explanation_days < 2555.
13. Standard Decision Output
This bot returns a SettlementReport object. See SettlementReport schema.
{
"report_id": "stl_posttradeexplainer_01HX9Z",
"bot_id": "gov.posttradeexplainer",
"event_type": "TRADE_EXPLAINED",
"fill_id": "fill_00a1b2c3d4e5f6a7",
"strategy": "sports-model",
"signal_summary": "Model edge 4.2 bps on YES leg of market 0x9b0c",
"guard_votes": [
{
"bot": "liquidityguard",
"vote": "pass"
},
{
"bot": "portfolioguard",
"vote": "pass"
}
],
"size_rationale": "Position sized at 80% of max_size_pusd due to LiquidityGuard spread signal",
"fill_price": 0.621,
"fill_size_pusd": 430.0,
"builder_fee_pusd": 1.075,
"plain_english": "Bought 430 pUSD of YES on market 0x9b0c at 0.621 \u2014 sports model detected 4.2 bps edge; all risk checks passed.",
"report_kind": "SettlementReport",
"topic": "polytraders.reports.settlement",
"retained_until": "2033-05-09"
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
TRADE_EXPLAINED | INFO | A fill was successfully explained and a SettlementReport emitted. | Log and store. | Your trade record is available. |
EXPLANATION_ARCHIVE_UNAVAILABLE | WARN | DecisionReport or RiskVote could not be fetched; partial explanation emitted. | Emit partial SettlementReport with explanation_complete=false. | Detailed trade explanation is temporarily unavailable. |
RETENTION_BELOW_REGULATORY_MINIMUM | WARN | retain_explanation_days is below 2555 (7 years). | Emit WARN; do not reduce retention. | |
EXPLANATION_MISSING_GUARD_VOTES | WARN | RiskVote records not found for the fill; explanation is incomplete. | Emit partial explanation; flag explanation_complete=false. | |
KILL_SWITCH_ACTIVE | WARN | KillSwitch was active at fill time; noted in explanation. | Include kill-switch note in explanation. | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_gov_posttradeexplainer_explanations_total | counter | count | complete | Total explanations emitted, labelled by completeness. |
polytraders_gov_posttradeexplainer_archive_fetch_latency_ms | histogram | ms | | Latency of archive fetch per explanation. |
polytraders_gov_posttradeexplainer_partial_explanations_total | counter | count | | Total partial explanations (archive unavailable). |
polytraders_gov_posttradeexplainer_retention_days | gauge | days | | Configured retention in days; should always be >= 2555. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
PostTradeExplainerArchiveUnavailable | rate(polytraders_gov_posttradeexplainer_partial_explanations_total[10m]) > 0 | P2 | #runbook-posttradeexplainer-archive |
PostTradeExplainerRetentionBreach | polytraders_gov_posttradeexplainer_retention_days < 2555 | P1 | #runbook-posttradeexplainer-retention |
16. Developer Reporting
{
"bot_id": "gov.posttradeexplainer",
"event_type": "EXPLANATION_COMPOSED",
"fill_id": "fill_00a1b2c3d4e5f6a7",
"archive_fetch_ms": 12,
"explanation_length": 280
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Trade explanation available | Your trade was made because the strategy detected a pricing edge. All risk checks passed. Full details are in the trade record. |
| Explanation archive unavailable | The detailed trade explanation is temporarily unavailable. The trade itself was executed correctly. |
18. Failure-Mode Block
| main_failure_mode | Report archive is unavailable; DecisionReport or RiskVote cannot be fetched, resulting in an incomplete explanation. |
|---|
| false_positive_risk | A stale DecisionReport is linked to the wrong fill, producing a misleading explanation. |
|---|
| false_negative_risk | Missing guard vote record causes the explanation to omit a guardrail outcome. |
|---|
| safe_fallback | If archive is unavailable, emit a partial explanation with fill metadata only and set explanation_complete=false. |
|---|
| required_dependencies | internal.report_archive (DecisionReport, RiskVote), internal.report_bus (ExecutionReport), clob_auth (fill confirmation) |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
ARCHIVE_UNAVAILABLE | Block reads from internal.reportArchive during fill processing | | Automatic retry when archive recovers. |
MISSING_RISK_VOTES | Delete RiskVote records for a specific trace_id | | No recovery needed — partial explanation is acceptable. |
RETENTION_CONFIG_BREACH | Set retain_explanation_days=30 in config | | Restore retain_explanation_days to >= 2555. |
20. State & Persistence
Cold-start recovery
On restart, resume consuming ExecutionReport events from the last processed offset.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | event-driven; one goroutine per ExecutionReport |
| Max in-flight | 100 |
| Idempotency key | fill_id |
| Per-call timeout (ms) | 2000 |
| Backpressure strategy | queue |
| Locking / mutual exclusion | none (append-only writes) |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|
internal.report_archive | DecisionReport and RiskVote fetched for each fill. | Records available by trace_id within 500ms of fill. |
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|
internal.post_trade_archive | | |
Sibling bots (same OrderIntent)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Internal report archive | https://archive.internal | 99.9% | Emit partial explanation with explanation_complete=false. |
23. Security Surfaces
Abuse vectors considered
- Requesting explanations for fills not belonging to the requesting user
Mitigations
- SettlementReport records are scoped to wallet_address; read access requires authenticated user context
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | PostTradeExplainer consumes ExecutionReport records from V2 fills; all amounts in pUSD. Reads builder_fee_pusd from fill metadata. |
API surfaces declared
internalclob_auth
Networks supported
polygon
25. Versioning & Migration
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q4-2026 |
Migration history
| Date | From | To | Reason | Action taken |
|---|
| 2026-04-28 | n/a | v2-spec | Spec drafted post-CLOB-V2 cutover; bot not yet implemented | Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain) |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| Explanation composed with all required fields | Full DecisionReport and RiskVote available | SettlementReport with non-null plain_english and all guard_votes |
| Partial explanation emitted when archive unavailable | report_archive returns 503 | SettlementReport with explanation_complete=false; fill metadata present |
Integration Tests
| Test | Expected result |
|---|
| End-to-end: fill confirmed → ExecutionReport → SettlementReport with explanation emitted | SettlementReport on polytraders.reports.settlement; retained_until = now+7y |
Property Tests
| Property | Required behaviour |
|---|
| Every SettlementReport includes a non-empty plain_english field | Always true — even partial explanations include a fallback message |
27. Operational Runbook
PostTradeExplainer incidents are usually archive unavailability producing partial explanations. These are low-urgency unless retention is also breached.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
PostTradeExplainerArchiveUnavailable | | | | |
PostTradeExplainerRetentionBreach | | | | |
Manual overrides
Healthcheck
/internal/health/posttradeexplainer → green if Consuming ExecutionReport events; retention_days >= 2555; partial_explanations rate == 0; red if retention_days < 2555 or archive unreachable
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 |