Review: Baby Betty contradictory jersey state (manual data fix)

review-393-2026-03-27 Review

review needs-refinement

Verdict: NEEDS_REFINEMENT

Template Completeness

  • [x] Type
  • [x] Lineage — standalone, split from #170
  • [x] Repo
  • [x] What Broke — thorough description with SQL evidence
  • [x] Repro Steps — concrete SQL queries
  • [x] Expected Behavior
  • [x] Environment
  • [x] Acceptance Criteria — 3 items, all SQL-verifiable
  • [x] Constraints — bonus section, good detail
  • [x] Related — links #170 and project

Traceability

  • [ ] story:X label — missing. Acceptable: this is an operational data fix, not a user-facing story.
  • [x] arch:stripe-webhook — present on board item #393
  • [x] Forgejo issue — forgejo_admin/basketball-api#171, open

File Targets

N/A — ticket explicitly states "this is a manual data fix, not a code change." No file targets to verify. Confirmed the schema claims against the codebase:
  • [x] players.jersey_option — Enum(JerseyOption) at models.py:208, values: reversible, jersey_warmup, opt_out
  • [x] players.jersey_order_status — Enum(JerseyOrderStatus) at models.py:209, values: none, pending, paid, shipped
  • [x] orders.status — Enum(OrderStatus) at models.py:355, values: pending, paid, refunded, canceled
  • [x] Legacy opt-out path at jersey.py:264 sets jersey_option = opt_out for all parent's players
  • [x] Legacy checkout at jersey.py:319 sets jersey_order_status = pending for all parent's players

Repo Placement

OK. Issue filed on forgejo_admin/basketball-api which owns the database. Manual SQL fix targets the same database. No cross-repo concern.

Dependencies

  • #170 (webhook sync gap) — parent bug, board item #392, status: done. The code fix (jersey_sync.py) is already merged. No blocker.
  • #308 (reconciliation script) — in next_up. Related but independent — that ticket builds a general reconciliation tool. This ticket is a one-off manual fix. No blocker.

Acceptance Criteria

3 criteria, all verifiable via SQL SELECT after the fix:
  • [x] players record for id=161: jersey_option = NULL, jersey_order_status = none — verifiable
  • [x] orders record #14: status = canceled — verifiable
  • [x] Verified via SQL query after fix — verifiable
Criteria are clear and testable. Note: acceptance criterion 1 says jersey_option = NULL which is valid (nullable column, models.py:208 confirms nullable=True).

Blast Radius

ISSUE FOUND: The legacy jersey routes operate on ALL players under a parent (for p in parent.players at jersey.py:263-265 and jersey.py:318-319). If Baby Betty (id=161) shares a parent with sibling players, those siblings may also have contradictory state from the same legacy checkout flow. The ticket only addresses player id=161 — sibling players are not checked or fixed.
The executing agent should run:
If siblings exist with the same contradictory state, the fix SQL must include them.

Decomposition

No decomposition needed. 0 file targets, 3 acceptance criteria, estimated <2 minutes of agent work (SQL transaction).

Recommendation

One refinement needed before READY:
  • Add sibling player check to acceptance criteria. The legacy code operates on all players under a parent. Add a criterion: "Verified no sibling players under the same parent have contradictory jersey state. If they do, fix them in the same transaction." This prevents a partial fix that leaves related data inconsistent.
Once the sibling check is added to the Forgejo issue, this ticket is READY.