brackt/docs/superpowers/plans/2026-05-02-phase4-cleanup.md
Chris Parsons 81af8907cc
Add design + 5 phase plans: canonical tournament & participant layer
Spec and phased implementation plans for eliminating data duplication
across overlapping qualifying-points windows (golf, tennis, CS2) by
introducing canonical tournaments, participants, tournament_results,
and surface-Elo tables above the existing per-window layer.

- Phase 1a: rename existing per-window tables to season_* prefix
- Phase 1b: create canonical tables + nullable FKs
- Phase 2:  backfill canonical from existing in-flight windows
- Phase 3:  cutover - sync service, admin UI, simulator read switch
- Phase 4:  cleanup - drop deprecated tables and routes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 05:20:34 +00:00

13 KiB

Phase 4 — Cleanup Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Remove the per-window surface-Elo admin UI + backing table, remove the old batch-add-results wrapper route (keeping the canonical route as the only path), and strip compatibility shims. Leaves the canonical layer as the sole source of truth.

Architecture: Pure removal. No new behavior. Each removal is preceded by a verification step proving the removed code is no longer reachable.

Tech Stack: Drizzle migration, Vitest, Cypress.

Prerequisite: Phase 3 complete and green in production for at least 7 days with active draft activity. Tag phase-3-complete exists.

Reference: docs/superpowers/specs/2026-05-01-canonical-tournament-layer-design.md — Phase 4 section.


Task 1: Verify season_participant_surface_elos is no longer read

Why: Before dropping the table, prove nothing reads from it.

  • Step 1: Grep for reads

Run:

grep -rn "seasonParticipantSurfaceElos\|season_participant_surface_elos" app/ server/ scripts/ database/

Expected: only hits in database/schema.ts (the Drizzle export and relation). No app/service reads, no queries.

If any live read remains — stop. Phase 3 was incomplete; fix the read path to go canonical, redeploy, wait 24h, then return here.

  • Step 2: Production observability check

Check production logs and/or pg_stat_user_tables for activity on season_participant_surface_elos:

SELECT seq_scan, seq_tup_read, idx_scan, idx_tup_fetch, n_tup_upd, n_tup_del
FROM pg_stat_user_tables
WHERE relname = 'season_participant_surface_elos';

Check pg_stat_reset() was not called recently. If the table has seen reads/writes in the past 7 days (compared against a baseline), investigate before dropping.

  • Step 3: Commit a note in the plan

Paste the output of Steps 1 and 2 into a short PR description for the drop migration, documenting verification.


Task 2: Drop season_participant_surface_elos

Files:

  • Modify: database/schema.ts (remove export + relations)

  • Create: drizzle/XXXX_drop_season_participant_surface_elos.sql

  • Step 1: Remove the table from schema

In database/schema.ts, delete:

  • The seasonParticipantSurfaceElos pgTable(...) export.

  • The seasonParticipantSurfaceElosRelations export.

  • Any references in other *Relations exports (e.g., in seasonParticipantsRelations or sportsSeasonsRelations that include seasonParticipantSurfaceElos via many()).

  • Step 2: Generate migration

Run: npm run db:generate -- --name=drop_season_participant_surface_elos Expected: Migration with a single DROP TABLE "season_participant_surface_elos" statement.

  • Step 3: Verify snapshot chain

Run: npm run db:generate Expected: "No schema changes".

  • Step 4: Apply migration on staging and re-run tests

Run: npm run db:migrate Run: npm run test:all Expected: Green.

  • Step 5: Apply migration on production

Follow your normal deploy flow. After deploy, re-verify:

SELECT tablename FROM pg_tables WHERE tablename = 'season_participant_surface_elos';

Expected: no row returned.

  • Step 6: Commit
git add database/schema.ts drizzle/
git commit -m "migration: drop season_participant_surface_elos table

Canonical participant_surface_elos is the sole source of surface Elo
since Phase 3. No reads or writes observed in the past 7 days.
Part of Phase 4 canonical tournament layer cleanup."

Task 3: Remove per-window surface-Elo admin UI

Files:

  • Remove: app/routes/admin.sports-seasons.$id.surface-elo.tsx

  • Modify: app/routes.ts (remove the route registration)

  • Modify: any navigation links pointing at the removed page

  • Step 1: Find references to the removed route

Run:

grep -rn "surface-elo" app/ | grep -v "^\.\/app/routes/admin\.sports-seasons\.\\\$id\.surface-elo\.tsx"

Expected: admin navigation links + possibly dashboard; update them to point at /admin/participants/:id/surface-elo or the canonical participant list.

  • Step 2: Delete the route file
git rm app/routes/admin.sports-seasons.$id.surface-elo.tsx
  • Step 3: Remove route registration

In app/routes.ts, delete the line registering admin.sports-seasons.$id.surface-elo.tsx.

  • Step 4: Update any links

Edit each file found in Step 1 to redirect or remove the per-window link.

  • Step 5: Run full test suite

Run: npm run test:all Expected: Green. Any failing tests that specifically asserted on the old route should be updated or removed.

  • Step 6: Commit
git add app/routes.ts app/
git commit -m "remove: per-window surface-elo admin UI

Canonical participant surface-elo edit page (/admin/participants/:id/surface-elo)
is now the only path. Part of Phase 4 cleanup."

Task 4: Remove the batch-add-results wrapper from per-window route

Goal: The per-window scoring-event admin route still has a batch-add-results intent (added in Phase 3 as a wrapper that writes to canonical). Remove the intent so the route no longer accepts per-window result entry. Admins must use /admin/tournaments/:id instead.

Files:

  • Modify: the per-window route handling qualifying result entry (the one edited in Phase 3 Task 11; find via grep -l "batch-add-results" app/routes/)

  • Modify: any UI component that still posts to batch-add-results on that route

  • Step 1: Grep for usages of the wrapper

Run:

grep -rn "batch-add-results" app/

Cross-reference with Phase 3 changes — confirm the only live callers are UI components on per-window pages (which should have been deprecated in Phase 3 release notes).

  • Step 2: Remove the batch-add-results intent branch

In the per-window route action handler, delete the if (intent === "batch-add-results") { ... } branch. Other intents stay.

  • Step 3: Remove the Batch Result Entry UI from the per-window event page

Find the component that rendered the "Enter Results" form on per-window pages. Replace with a banner:

<div className="p-4 bg-blue-50 rounded">
  <p>Result entry has moved.</p>
  <Link to={`/admin/tournaments/${event.tournamentId}`} className="underline">
    Enter results for {event.name} here.
  </Link>
</div>

This assumes event.tournamentId is loaded in the route loader (it is, after Phase 1b).

  • Step 4: Remove related E2E tests that exercised the old form

Search cypress/e2e/ for tests that used the per-window batch form. Either update to use the new canonical flow or remove if covered by cypress/e2e/enter-tournament-result.cy.ts.

  • Step 5: Run full suite

Run: npm run test:all Expected: Green.

  • Step 6: Manual smoke

Run dev server, log in as admin, navigate to /admin/sports-seasons/<id>/events/<eventId> → verify the banner links to the canonical tournament page.

  • Step 7: Commit
git add app/
git commit -m "remove: per-window batch-add-results intent and UI

Result entry consolidated on /admin/tournaments/:id. Per-window event page
shows a deep link to the canonical flow. Part of Phase 4 cleanup."

Task 5: Remove unused compatibility shims

Goal: Any helper functions, type aliases, or dead code paths added during Phase 3 to keep old callers working.

  • Step 1: Grep for known shim markers

If Phase 3 left @deprecated JSDoc tags, find them:

grep -rn "@deprecated" app/ | grep -iE "canonical|tournament|participant"

For each, verify no live callers remain (grep -rn "<function-name>" app/), then delete.

  • Step 2: Check the intent prop default on BatchResultEntry

If Phase 3 Task 8 added an intent prop with default "batch-add-results", change the default to "batch-upsert-results" now (the canonical intent) since the old one is gone. Or, if the component is now only used from the canonical route, make the prop required and remove the default.

  • Step 3: Run full suite + typecheck

Run: npm run typecheck && npm run test:all Expected: Green.

  • Step 4: Commit
git add app/
git commit -m "chore: remove deprecated shims after Phase 3 cutover"

Task 6: Final documentation sweep

Files:

  • Modify: docs/agents/database.md — document canonical vs per-window split

  • Modify: docs/agents/domain-models.md

  • Create: a short section in docs/ describing how to create a new rolling window (user-facing operator guide)

  • Step 1: Update docs/agents/database.md

Add a section:

## Canonical vs. Per-Window Tables (Qualifying-Points Sports)

Golf, tennis, and CS2 use a split model:

- **Canonical** (real-world identity, shared across seasons):
  - `tournaments` — real-world tournaments (Wimbledon 2026)
  - `participants` — real-world players/teams (Djokovic, NAVI)
  - `tournament_results` — final placements
  - `participant_surface_elos` — tennis surface-specific Elo
- **Per-window** (specific to a `sports_seasons` row):
  - `season_participants` — draftable roster for this window
  - `season_participant_expected_values` — EV under this window's 4-event set
  - `season_participant_qualifying_totals` — running QP sum for this window
  - `season_participant_results` — final placement for fantasy scoring
  - `event_results` — per-scoring-event placements (derived from `tournament_results`)

Enter tournament results at `/admin/tournaments/:id`. The `syncTournamentResults`
service fans out to every window linked via `scoring_events.tournament_id`.
  • Step 2: Update docs/agents/domain-models.md

Replace references to the old per-window participant model with the split canonical/per-window description.

  • Step 3: Create operator guide
# docs/operations/rolling-windows.md
# Creating a Rolling Qualifying-Points Window

When a new tennis/golf/CS2 "season" should open (typically every 3 months), follow these steps:

1. Go to `/admin/tournaments` and ensure the 4 real-world tournaments exist.
   If a tournament is missing, click "New Tournament" on its sport page.
2. Go to `/admin/sports-seasons/new` and pick the sport.
3. In the tournament picker, select the 4 tournaments that define this window.
4. In the roster picker, select the canonical participants you want in the draft pool.
5. Configure scoring (QP point values, roster size, etc.).
6. Submit. The new `sports_seasons` row is created with scoring_events linked to
   the canonical tournaments and season_participants linked to canonical participants.

When a real-world tournament completes, enter its results once at
`/admin/tournaments/<id>` — they automatically propagate to every window that
contains it.
  • Step 4: Commit
git add docs/
git commit -m "docs: canonical tournament layer — agent guides + operator docs"

Task 7: Final regression + tag

  • Step 1: Full test suite

Run: npm run test:all Expected: Green.

  • Step 2: Baseline diff (sanity)

Re-run baseline capture and diff against Phase 1 fixtures:

npx tsx scripts/capture-baseline.ts --out=/tmp/post-phase4
diff test-fixtures/baselines/qp-totals-pre-phase1.json /tmp/post-phase4/qp-totals-*.json
diff test-fixtures/baselines/event-results-pre-phase1.json /tmp/post-phase4/event-results-*.json

Expected: Empty (no QP math drift across the entire migration).

  • Step 3: Tag
git tag phase-4-complete
git tag canonical-tournament-layer-complete

The second tag marks the full feature complete.


Self-Review Checklist

  • season_participant_surface_elos table does not exist in prod.
  • /admin/sports-seasons/:id/surface-elo route returns 404.
  • Per-window result entry form has been replaced with a link to the canonical route.
  • grep -rn "batch-add-results" app/ returns zero results.
  • docs/agents/database.md describes the canonical/per-window split.
  • Operator guide docs/operations/rolling-windows.md exists.
  • npm run test:all is green.
  • Baseline diff is empty.
  • Tag canonical-tournament-layer-complete exists on main.

Rollback

Dropping season_participant_surface_elos is reversible if needed (no row is dropped in Phase 4 — the data was never used for anything after Phase 3). To rollback:

  1. git revert the drop migration commit.
  2. Re-generate the season_participant_surface_elos table via Drizzle migration.
  3. Re-seed it from participant_surface_elos keyed via season_participants.participantId.

Other Phase 4 removals (routes, UI, shims) are pure code and reversible via git revert.