When an artifact's lifecycle state changes (validated, deprecated, etc.), all open positions
on that artifact must settle. Long holders profit when artifacts are validated; short holders
profit when artifacts are deprecated. Settlement is the mechanism that makes market
predictions consequential.
settlement.py module with:settle_artifact(artifact_id, new_lifecycle_state) — settle all positionssettle_position(position_id, settlement_price) — settle individual positioncompute_settlement_pnl(position, settlement_price) — P&L calculationdistribute_bounty(bounty_id, claimants) — distribute bounty tokens
POST /api/artifacts/{id}/lifecycle-transition)challenged state (no close, no cancel — 409 on cancel; status set to 'challenged')token_ledger with reason='settlement:profit' or settlement:lossactor_reputation)GET /api/market/settlements?artifact_id=X — settlement historyevents table)flagged then back to listed, positionsexch-cm-03-BID — Positions to settleexch-cm-05-PORT — Portfolio updates on settlementexch-qm-03-LIFE — Lifecycle transitions trigger settlementexch-cm-02-EARN — Correct predictions mint reward tokensscidex/exchange/, api.py, scripts/settlement.pyscripts/settlement.py (479 lines) with core logic — moved to production modulescidex/exchange/settlement.py with:compute_settlement_pnl(position, settlement_price, lifecycle_state) — P&L logic per specsettle_position(position_id, settlement_price, lifecycle_state, db) — individual settlement with atomic token_accounts + token_ledger + actor_reputation updatesettle_artifact(artifact_id, new_lifecycle_state, db) — batch settle all open positions; challenged state freezes (status='challenged') instead of settlingdistribute_bounty(bounty_id, claimants, db) — proportional token distributionget_settlement_history(artifact_id, agent_id, limit, db) — settlement audit trail_log_settlement_notification() — logs to events table (non-real-time per spec)
api.py (api.py changes):GET /api/market/settlements?artifact_id=X&agent_id=Y&limit=N — settlement history endpointPOST /api/artifacts/{artifact_id}/lifecycle-transition — triggers settle_artifact for validated/deprecated/flagged/challenged transitions, logs to artifact_lifecycle_historycompute_settlement_pnl against all 6 spec scenarios — all passget_settlement_history against live DB: 1 existing settled record foundsettle_artifact, breaking atomicity — a settlement failure would return 500 with the artifact already transitioned.db.commit(), passed db=db to settle_artifact(resolved_id, new_state, db=db), moved db.commit() to after settlement returns. Settlement module already supported an external-db path (own_db = db is None) and skips its own commit when db is supplied, so all writes (artifact state, lifecycle history, positions, token_accounts, token_ledger) now commit atomically or roll back together.Evidence:
api.py lines 24673–24696: lifecycle UPDATE, history INSERT, and settle_artifact(resolved_id, new_state, db=db) all use the same db connection; db.commit() fires only after settlement returns; any exception triggers db.rollback().scidex/exchange/settlement.py: settle_artifact and settle_position check own_db = db is None and only self-commit when no external db is passed — confirmed at lines 221, 256 (if own_db: db.commit()).f9b1a0d56 — "[Exchange] Fix lifecycle-transition atomicity: settle within same transaction [task:exch-cm-06-SETL]", squash-merged to main as f8284bf5a.f9b1a0d56 before the prior squash merge. All acceptance criteria remain satisfied on current main HEAD 6e2b3b699.