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 LayerGovernance6.13 UserActivityLedger

6.13 UserActivityLedger

Governance Governance Service Explain PLANNED Spec started capital · Indirect P3 · Reporting & event store pending stub

UserActivityLedger records every user-initiated action — strategy starts, parameter changes, halts, and overrides — as a SettlementReport, providing a per-user, per-session compliance ledger retained for 7 years.

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

LayerGovernance  Governance
Bot classGovernance Service
AuthorityExplain
StatusPLANNED
ReadinessSpec started
Runs beforeNothing — runs on every user action event
Runs afterAny user-initiated action: strategy start, parameter change, halt, override
Applies toAll user wallet sessions across the Polytraders platform
Default modeshadow_only
User-visiblesummary-only
Developer ownerPolytraders core

2. Purpose

UserActivityLedger records every user-initiated action — strategy starts, parameter changes, halts, and overrides — as a SettlementReport, providing a per-user, per-session compliance ledger retained for 7 years.

3. Why This Bot Matters

  • No user activity ledger

    Regulatory requests for user action history cannot be fulfilled; compliance audit fails.

  • User activity not linked to fills

    It is impossible to reconstruct which user action led to which trade; audit trail is broken.

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
None — UserActivityLedger consumes internal event stream onlyinternalNoN/A

5. Required Internal Inputs

InputSourceRequired?Use
User action events from the platform event streaminternal.event_streamYesRecord every user-initiated action with wallet address, session ID, and action type.
ExecutionReport for fills linked to user sessionsinternal.report_busYesLink fills to the user session that initiated the strategy.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
retain_days255518252555Retention period for ledger records in days (default 7 years).
scrub_on_account_closeFalseNoneNoneWhether to scrub PII on account close (retain anonymised records).

7. Detailed Parameter Instructions

retain_days

What it means

Retention period for ledger records in days (default 7 years).

Default

{ "retain_days": 2555 }

Why this default matters

7-year retention is required by financial regulations.

Threshold logic

ConditionAction
retain_days < 2555WARN — below regulatory minimum

Developer check

if p.retain_days < 2555: emit('RETENTION_BELOW_REGULATORY_MINIMUM')

User-facing English

Your activity history is kept for 7 years as required by regulation.

scrub_on_account_close

What it means

Whether to scrub PII on account close (retain anonymised records).

Default

{ "scrub_on_account_close": false }

Why this default matters

Default false preserves full records; scrubbing is opt-in per jurisdiction.

Threshold logic

ConditionAction
scrub_on_account_close=trueReplace PII fields with hashed identifiers on account close

Developer check

if p.scrub_on_account_close: record.wallet = hash(record.wallet)

User-facing English

— not yet authored —

8. Default Configuration

{
  "bot_id": "gov.useractivityledger",
  "version": "0.1.0",
  "mode": "shadow_only",
  "defaults": {
    "retain_days": 2555,
    "export_format": "jsonl",
    "scrub_on_account_close": false,
    "publish_to_user": true
  }
}

9. Implementation Flow

  1. Subscribe to user action events on the internal platform event stream.
  2. For each event, record: wallet_address, session_id, action_type, parameters, timestamp.
  3. Link fill events to user sessions via trace_id from ExecutionReport.
  4. Emit SettlementReport(event_type=USER_ACTION_RECORDED) on every action.
  5. Enforce retain_days; purge records older than retention window (or scrub PII if scrub_on_account_close=true).
  6. Provide a read API for compliance export in jsonl format.

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 USER EVENTS ----
FUNCTION onUserAction(event):
  record = {
    wallet_address: event.wallet,
    session_id:     event.session_id,
    action_type:    event.action_type,
    action_params:  event.params,
    trace_id:       event.trace_id,
    recorded_at:    now()
  }
  IF postgres.exists('user_activity_ledger', idempotency_key=event.event_id):
    RETURN  // deduplicate replay
  postgres.insert('user_activity_ledger', record)
  EMIT SettlementReport(event_type='USER_ACTION_RECORDED', ...record,
    retained_until=now() + days(config.retain_days))

// ---- LINK FILL TO SESSION ----
FUNCTION onExecutionReport(execReport):
  ledgerRows = postgres.select('user_activity_ledger',
    WHERE trace_id = execReport.trace_id)
  FOR row IN ledgerRows:
    row.fill_ids.append(execReport.fill_id)
    postgres.upsert('user_activity_ledger', row)
    EMIT SettlementReport(event_type='ACTION_LINKED_TO_FILL', ...)

SDK calls used

  • postgres.insert('user_activity_ledger', record)
  • postgres.select('user_activity_ledger', WHERE trace_id=...)
  • alerting.emit('RETENTION_BELOW_REGULATORY_MINIMUM')

Complexity: O(1) per user action; O(R) for fill linking where R = ledger rows per trace_id

11. Wire Examples

Input — what arrives on the wire

{
  "label": "User action event",
  "source": "internal.event_stream",
  "payload": {
    "event_id": "evt_01HX9Z",
    "wallet": "0xdeadbeef00000000000000000000000000000001",
    "session_id": "sess_01HX9Z",
    "action_type": "STRATEGY_START",
    "params": {
      "strategy": "sports-model"
    },
    "trace_id": "trc_01HX9Z"
  }
}

Output — what the bot emits

{
  "label": "SettlementReport — USER_ACTION_RECORDED",
  "payload": {
    "report_id": "stl_activity_01HX9Z",
    "event_type": "USER_ACTION_RECORDED",
    "wallet_address": "0xdeadbeef00000000000000000000000000000001",
    "action_type": "STRATEGY_START",
    "report_kind": "SettlementReport",
    "topic": "polytraders.reports.settlement",
    "retained_until": "2033-05-09"
  }
}

12. Decision Logic

APPROVE

Not applicable — UserActivityLedger is a pure audit recorder.

RESHAPE_REQUIRED

Not applicable.

REJECT

Not applicable.

WARNING_ONLY

Emits RETENTION_BELOW_REGULATORY_MINIMUM if retain_days < 2555.

13. Standard Decision Output

This bot returns a SettlementReport object. See SettlementReport schema.

{
  "report_id": "stl_useractivityledger_01HX9Z",
  "bot_id": "gov.useractivityledger",
  "event_type": "USER_ACTION_RECORDED",
  "wallet_address": "0xdeadbeef00000000000000000000000000000001",
  "session_id": "sess_01HX9Z",
  "action_type": "STRATEGY_START",
  "action_params": {
    "strategy": "sports-model",
    "max_size_pusd": 500.0
  },
  "trace_id": "trc_01HX9Z",
  "recorded_at": "2026-05-09T10:00:00Z",
  "report_kind": "SettlementReport",
  "topic": "polytraders.reports.settlement",
  "retained_until": "2033-05-09"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
USER_ACTION_RECORDEDINFOA user action was recorded in the ledger.Log and emit SettlementReport.
ACTION_LINKED_TO_FILLINFOA user action was linked to a fill via trace_id.Update ledger record.
RETENTION_BELOW_REGULATORY_MINIMUMWARNretain_days < 2555.Emit WARN; refuse config change.
LEDGER_WRITE_FAILEDWARNPostgres write failed; event buffered in memory.Buffer; emit WARN; flush on reconnect.
KILL_SWITCH_ACTIVEWARNKillSwitch active; KillSwitch event recorded in ledger.Record kill-switch event as USER_ACTION.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_gov_useractivityledger_actions_totalcountercountaction_typeTotal user actions recorded by type.
polytraders_gov_useractivityledger_fill_links_totalcountercountTotal fill-to-session links created.
polytraders_gov_useractivityledger_buffer_sizegaugecountEvents buffered in memory awaiting Postgres write.
polytraders_gov_useractivityledger_retention_daysgaugedaysConfigured retention; must be >= 2555.

Alerts

AlertConditionSeverityRunbook
UserActivityLedgerBufferGrowingpolytraders_gov_useractivityledger_buffer_size > 500P2#runbook-useractivityledger-buffer
UserActivityLedgerRetentionBreachpolytraders_gov_useractivityledger_retention_days < 2555P1#runbook-useractivityledger-retention

16. Developer Reporting

{
  "bot_id": "gov.useractivityledger",
  "event_type": "ACTION_LINKED_TO_FILL",
  "session_id": "sess_01HX9Z",
  "fill_id": "fill_00a1b2c3d4e5f6a7",
  "linked_at_ms": 1746792060000
}

17. Plain-English Reporting

SituationUser-facing explanation
User requests activity historyYour complete activity history is available for the past 7 years.
Account close with PII scrubYour personally identifiable information has been removed from records, but anonymised activity history is retained for compliance.

18. Failure-Mode Block

main_failure_modePlatform event stream is unavailable; user actions are not recorded in real time.
false_positive_riskA replayed event is recorded twice due to missing deduplication.
false_negative_riskA user action fires before the ledger subscribes; the action is missing from the ledger.
safe_fallbackBuffer missed events in memory; flush on reconnect. Use idempotency key to deduplicate replays.
required_dependenciesinternal.event_stream, internal.report_bus (ExecutionReport), Postgres ledger store

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
POSTGRES_UNAVAILABLEKill Postgres connection during high-volume user actionsAutomatic flush when Postgres recovers.
DUPLICATE_EVENT_REPLAYReplay an already-recorded event_idIdempotent — no action needed.
RETENTION_BREACHSet retain_days=30 in configRestore retain_days to >= 2555.

20. State & Persistence

Cold-start recovery

On restart, flush memory buffer; resume event stream consumption from last committed offset.

21. Concurrency & Idempotency

AspectSpecification
Execution modelevent-driven; single consumer goroutine on user event stream
Max in-flight200
Idempotency keyevent_id
Per-call timeout (ms)1000
Backpressure strategybuffer-in-memory
Locking / mutual exclusionPostgres unique constraint on event_id

22. Dependencies

Depends on (must run first)

BotWhyContract
internal.event_streamAll user action events are sourced from the platform event stream.Events include wallet_address, session_id, action_type, and trace_id.

Emits to (downstream consumers)

BotWhyContract
internal.post_trade_archive

Sibling bots (same OrderIntent)

BotWhyContract
gov.posttradeexplainerPostTradeExplainer produces trade explanations; UserActivityLedger links them to user sessions.trace_id is shared between both systems.

External services

ServiceEndpointSLA assumedOn failure
Internal Postgres (ledger)postgres://internal99.9%Buffer events in memory; flush on reconnect; idempotency prevents duplicates.

23. Security Surfaces

Abuse vectors considered

  • Querying another user's activity ledger without authorisation

Mitigations

  • All ledger reads require authenticated user context; wallet_address is verified against session token

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesUserActivityLedger records user actions in pUSD denomination; no direct CLOB calls.

API surfaces declared

internal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ4-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
STRATEGY_START action recorded with all required fieldsUser action event with action_type=STRATEGY_STARTSettlementReport with wallet_address, session_id, and action_params
PII scrubbed on account closescrub_on_account_close=true; account close eventwallet_address replaced with hash; all other fields retained

Integration Tests

TestExpected result
Fill linked to user session via trace_idSettlementReport updated with fill_id cross-reference

Property Tests

PropertyRequired behaviour
All records retained for >= 2555 daysAlways true — retention enforcement is mandatory

27. Operational Runbook

UserActivityLedger incidents are usually Postgres unavailability or retention config breaches.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
UserActivityLedgerBufferGrowing
UserActivityLedgerRetentionBreach

Manual overrides

Healthcheck

/internal/health/useractivityledger → green if Postgres reachable; buffer_size == 0; retention_days >= 2555; red if retention_days < 2555 or buffer_size > 500

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
Idempotency deduplication unit test passesCIPass

Promote to Limited live

GateHow measuredThreshold
PII scrub integration test passesIntegration testPass

Promote to General live

GateHow measuredThreshold
Compliance team sign-off on 7-year retention schema and PII scrub policyCompliance reviewPass

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