Skip to content

ESPN: approval-gated roster edits (add/drop) - #176

Merged
warnerktsang merged 10 commits into
mainfrom
espn-roster-edits
Oct 30, 2026
Merged

warnerktsang merged 10 commits into
mainfrom
espn-roster-edits

Conversation

@warnerktsang

Copy link
Copy Markdown
Collaborator

What

Approval-gated roster edits (add/drop) for the ESPN module — the module's second write, gated harder than lineup moves because it changes who is ON the roster. Server-side propose → approve, following the gate shape Resy established (and enforcing it in the backend, not just the agent layer):

  1. Propose — POST /espn/roster/edits {add_player_id?, drop_player_id?} (a swap is both; add-only and drop-only work too). Validates against the live league: add target is a free agent in the league-scoped kona pool (waivers.py's fetch, reused), drop target is on the account's own roster, post-edit roster is legal under the league's slot counts with the lineup planner's IR accounting. Persists a pending edit (new espn_roster_edits table, migration 0014) with the validated player snapshot, whether the add target is on waivers, a human preview, and a 30-minute expiry. Submits nothing to ESPN.
  2. Approve — POST /espn/roster/edits/{id}/approve executes the pending, unexpired edit as ONE ESPN transaction with both moves. The edit is claimed atomically (pending → executing) before submitting, league state is re-validated at approve time (stale edits fail closed), and the outcome is recorded on the row: executed / waiver_pending (ESPN parked it as a waiver claim; bid 0, no FAAB in v1) / failed with ESPN's error. Double-approve → 409, another account's edit → 404; never double-submits.
  3. Read — GET /espn/roster/edits (list, optional ?status=) and GET /espn/roster/edits/{id}: the audit trail, like lineup runs.

MCP mirrors the routes (propose_roster_edit, approve_roster_edit, get_roster_edits) as two separate tools — the pair is the gate. Tenant isolation is the module rule throughout: token → account → league/team, every query scoped by account_id.

Nothing is wired into the scheduler or lineup automation; the waiver digest stays recommendations-only. Edits happen only on an explicit propose + approve.

Transaction format assumption

The fixtures don't cover ADD/DROP submissions, so the shape is derived from the mTransactions2 READ payload this module already handles (see logic.get_transactions and live league history): a swap is one transaction of type: "FREEAGENT" — or "WAIVER" with bidAmount: 0 when the add target is on waivers — whose items are {playerId, type: "ADD", fromTeamId: 0, toTeamId: <team>} and {playerId, type: "DROP", fromTeamId: <team>, toTeamId: 0} (team 0 = free-agent pool), in the same envelope the proven lineup write uses. The client stamps items itself (post_roster_transaction) so no caller can smuggle another move type; trades have no code path. First live exercise will be a supervised integration test after merge/deploy.

Also

  • Migration 0014 + beli_schema_contract.sql + contract version 13 → 14 (the runner requires version == latest migration). Two existing version asserts in test_espn_migration.py updated, plus new schema/contract coverage.
  • docs/espn.md: roster-edits section + route table (and the stale "there never will be add/drop endpoints" line corrected).
  • Local connector CLI (not in this repo) gained swap / swap-approve / swaps verbs and CONNECTOR.md documents the gate discipline.

Tests

  • New tests/test_espn_roster_edits.py (19 tests): propose validation failures (rostered add, unknown add, unowned drop, illegal roster size), add-only with an open slot, snapshot + preview, approve = single transaction with both moves, waiver-pending recorded, double-approve rejected, expiry rejected + marked, submit failure recorded, stale-at-approve fails closed, tenant isolation, client wire shape (FREEAGENT/WAIVER stamping), HTTP round-trip + error mapping.
  • ESPN scope locally: 525 passed (505 baseline + 20 new). Full backend suite: no new failures (6 failures in dice/trips local-harness tests reproduce identically on clean main in this sandbox — dotenv/loopback environment guards).

@warnerktsang
warnerktsang merged commit 0a6c3d1 into main Oct 30, 2026
9 checks passed
@warnerktsang
warnerktsang deleted the espn-roster-edits branch October 30, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant