MOPC-App/docs/unified-architecture-redesign/10-migration-strategy.md

28 KiB
Raw Blame History

Migration Strategy

Overview

The migration from Pipeline/Track/Stage to Competition/Round follows a 4-phase approach designed to minimize risk. New tables are added alongside old ones, data is backfilled, code is migrated, and finally old tables are dropped. Feature flags allow granular control of the transition.


Migration Phases

Phase 1: Schema Additions (Non-Breaking)

Add all new tables and enums alongside existing tables. No existing tables are modified or dropped.

New tables added:

  • Competition (parallel to Pipeline)
  • Round (parallel to Stage)
  • JuryGroup, JuryGroupMember
  • SubmissionWindow, SubmissionFileRequirement
  • RoundSubmissionVisibility
  • ProjectRoundState (parallel to ProjectStageState)
  • DeliberationSession, DeliberationVote, DeliberationResult, DeliberationParticipant
  • ResultLock, ResultUnlockEvent
  • MentorMessage
  • AssignmentIntent, AssignmentException
  • SubmissionPromotionEvent
  • AwardWinner

New enums added:

  • CompetitionStatus: DRAFT, ACTIVE, COMPLETED, ARCHIVED
  • RoundType: INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
  • RoundStatus: DRAFT, READY, ACTIVE, COMPLETED, SKIPPED
  • CapMode: HARD, SOFT, NONE
  • DeadlinePolicy: HARD, FLAG, GRACE
  • DeliberationMode: SINGLE_WINNER_VOTE, FULL_RANKING
  • DeliberationStatus: OPEN, VOTING, TALLYING, RUNOFF, LOCKED
  • TieBreakMethod: RUNOFF, ADMIN_DECIDES, SCORE_FALLBACK
  • DeliberationParticipantStatus: REQUIRED, ABSENT_EXCUSED, REPLACED, REPLACEMENT_ACTIVE
  • AwardRoutingMode: STAY_IN_MAIN, SEPARATE_POOL
  • AwardEligibilityMode: AI_SUGGESTED, MANUAL, ALL_ELIGIBLE, ROUND_BASED
  • WinnerDecisionMode: JURY_VOTE, SINGLE_JUDGE
  • MentorMessageRole: MENTOR, APPLICANT, ADMIN
  • PromotionSourceType: MENTOR_FILE, ADMIN_REPLACEMENT
  • AssignmentIntentSource: INVITE, ADMIN, SYSTEM

Rollback: Drop new tables and enums. Zero impact on existing system.


Phase 2: Data Migration (Backfill)

Backfill new tables from existing data. Old tables remain populated and active.

Mapping table:

Old Model New Model Mapping Logic
Pipeline Competition 1:1. Copy id, programId, name, status, settings → typed config split
Track (MAIN) (eliminated) Main track's stages become Competition's rounds directly
Track (AWARD) SpecialAward One SpecialAward per AWARD track
Stage Round 1:1. Copy type mapping (see below), order, status
Stage.configJson Round.configJson Parse and validate against typed schema for the round type
ProjectStageState ProjectRoundState Map stageId → roundId, trackId dropped
Judge assignments JuryGroupMember + assignments Create JuryGroups from distinct jury configurations
SpecialAward SpecialAward (enhanced) Add routing mode, eligibility mode fields

Stage type to Round type mapping:

Old StageType New RoundType Notes
INTAKE INTAKE Direct mapping
FILTER FILTERING Renamed for clarity
EVALUATION EVALUATION Direct mapping (used for both Jury 1 and Jury 2)
SELECTION (absorbed) Selection logic moves into evaluation round config
LIVE_FINAL LIVE_FINAL Direct mapping
RESULTS (absorbed) Results are part of the deliberation round
(new) SUBMISSION New round type for multi-round document collection
(new) MENTORING New round type for mentor collaboration
(new) DELIBERATION New round type replacing confirmation

Backfill script: A TypeScript migration script reads all existing Pipelines/Tracks/Stages and creates corresponding Competition/Round records. The script is idempotent (can be run multiple times safely).

Rollback: Delete backfilled data from new tables. Old tables unchanged.


Phase 3: Code Migration

Update all services, routers, and UI to use new models. Feature flags control which code path is active.

Feature flag strategy:

const FEATURE_FLAGS = {
  USE_COMPETITION_MODEL: false,     // Phase 3: switch to Competition/Round queries
  USE_JURY_GROUPS: false,           // Phase 3: switch to JuryGroup-based assignment
  USE_DELIBERATION: false,          // Phase 6: switch to new deliberation model
  USE_MENTOR_WORKSPACE: false,      // Phase 4: enable enhanced mentor features
  USE_SUBMISSION_WINDOWS: false,    // Phase 4: enable multi-round submissions
  HIDE_LEGACY_PIPELINE_UI: false,   // Phase 7: hide old Pipeline/Stage UI
}

Dual-read pattern: During transition, services can read from both old and new tables:

async function getCompetitionContext(id: string) {
  if (FEATURE_FLAGS.USE_COMPETITION_MODEL) {
    return getFromCompetitionModel(id)
  }
  return getFromPipelineModel(id) // legacy path
}

Router migration order:

  1. pipeline.tscompetition.ts (create new router, keep old active behind flag)
  2. stage.tsround.ts (same approach)
  3. Remove track.ts (logic absorbed into competition + specialAward routers)
  4. Update assignment.ts to use JuryGroup model
  5. Update evaluation.ts to use Round model
  6. Add deliberation.ts (new router)
  7. Update mentor.ts with workspace features
  8. Update all UI pages to use new routers

Rollback: Flip feature flags back to legacy. Both code paths exist during this phase.


Phase 4: Cleanup (Point of No Return)

Drop old tables and remove legacy code paths. This is irreversible.

Tables dropped:

  • Pipeline
  • Track
  • Stage
  • ProjectStageState
  • Legacy configJson schemas

Enums dropped:

  • StageType (replaced by RoundType)
  • TrackKind (MAIN/AWARD — eliminated)
  • RoutingMode (SHARED/EXCLUSIVE — replaced by AwardRoutingMode)
  • WinnerProposalStatus (replaced by DeliberationStatus)
  • WinnerApprovalRole (replaced by DeliberationParticipantStatus)

Models dropped:

  • WinnerProposal (replaced by DeliberationSession)
  • WinnerApproval (replaced by DeliberationVote)

Code removed:

  • All feature flag conditionals (only new path remains)
  • All dual-read logic
  • All legacy router files
  • All legacy service files

Rollback: Restore from database backup. This phase should only be executed after thorough testing in Phases 13 and the burn-in period in Phase 8 of the implementation roadmap.


Data Mapping Reference

Complete Field Mapping

Old Field New Field Notes
Pipeline.id Competition.id Preserve IDs for foreign key consistency
Pipeline.programId Competition.programId Direct
Pipeline.name Competition.name Direct
Pipeline.description Competition.description Direct
Pipeline.status Competition.status Map to CompetitionStatus enum
Pipeline.settingsJson Competition.settingsJson Carry forward, validate
Stage.id Round.id Preserve IDs
Stage.trackId (dropped) No more track reference
Stage.pipelineId Round.competitionId Rename
Stage.type Round.type Map StageType → RoundType
Stage.order Round.order Direct
Stage.status Round.status Map to RoundStatus
Stage.configJson Round.configJson Validate against typed schema
Stage.startsAt Round.startsAt Direct
Stage.endsAt Round.endsAt Direct
ProjectStageState.stageId ProjectRoundState.roundId Rename
ProjectStageState.trackId (dropped) No more track
ProjectStageState.projectId ProjectRoundState.projectId Direct
ProjectStageState.state ProjectRoundState.state Map values

Additional FK Migrations (stageId → roundId)

These existing models have stageId foreign keys that must be renamed to roundId. They are NOT being replaced by new models — just renamed:

Model Old FK New FK Notes
AudienceVote stageId roundId Live voting records
COIDeclaration stageId roundId Conflict of interest declarations
ProjectStatusHistory stageId roundId Project state change log
DigestLog stageId roundId Notification digest tracking
MentorNote stageId roundId Mentor notes (legacy model, still used)
MentorMilestone stageId roundId Mentor milestone definitions
PartnerStageAccess stageId roundId Rename model to PartnerRoundAccess
LearningResource stageId roundId Learning resources linked to rounds
TaggingJob stageId roundId AI tagging job references
FilteringResult stageId roundId Filtering results per project
FilteringJob stageId roundId Filtering batch job records
EvaluationReminder stageId roundId Cron-triggered reminder records
GracePeriod stageId roundId Grace period extensions
LiveProgressCursor stageId roundId Live ceremony cursor
LiveVotingSession stageId roundId Live voting session

Migration approach: These are simple column renames with FK constraint updates. Run as a single migration after the main table additions in Phase 1.

New Field Additions (Non-Breaking)

These fields are added to existing new models during Phase 1. All have defaults, so they are non-breaking:

Model Field Type Default Purpose
Round purposeKey String? null Optional analytics tag
JuryGroupMember role JuryGroupMemberRole MEMBER Replaces isLead: Boolean
SubmissionWindow isLocked Boolean false Manual lock independent of window close
AssignmentIntent status AssignmentIntentStatus PENDING Proper lifecycle enum

Data migration for isLead → role: For existing data where isLead = true, set role = CHAIR. For isLead = false, set role = MEMBER. Drop isLead column after backfill.

New Enums (Phase 1)

Enum Values Used By
JuryGroupMemberRole CHAIR, MEMBER, OBSERVER JuryGroupMember.role
AssignmentIntentStatus PENDING, HONORED, OVERRIDDEN, EXPIRED, CANCELLED AssignmentIntent.status

Pre-Migration Checklist

Before starting Phase 1:

  • All type definitions finalized (Phase 0 of roadmap)
  • Database backup taken
  • Migration script written and tested on staging
  • Feature flags infrastructure in place
  • Rollback procedures documented and tested
  • All team members briefed on migration plan

Before starting Phase 4 (cleanup):

  • All feature flags pointing to new code paths
  • 72-hour burn-in period completed with zero critical errors
  • All release gates AF passed (see 12-observability-and-release-gates.md)
  • Database backup taken (point-of-no-return backup)
  • Rollback from backup tested on staging

Timeline Alignment

Migration Phase Roadmap Phase When
Phase 1: Schema additions Implementation Phase 1 Weeks 23
Phase 2: Data backfill Implementation Phase 1 Weeks 34
Phase 3: Code migration Implementation Phases 36 Weeks 412
Phase 4: Cleanup Implementation Phase 7 Week 1213

See 09-implementation-roadmap.md for the full implementation timeline.


System Inventory Appendix

Complete file-by-file migration effort estimate. Risk: L = Low (rename only), M = Medium (logic changes), H = High (rewrite).

Database & Schema (3 files, ~28 hours)

File Changes Risk
prisma/schema.prisma 40+ model/enum updates, 17+ FK renames H
prisma/seed.ts 300+ lines referencing Pipeline/Stage M
prisma/migrations/* New migration files for all schema changes M

tRPC Routers (24 files, ~104 hours)

File Changes Risk
src/server/routers/pipeline.ts Rename to competition.ts, update all queries H
src/server/routers/stage.ts Rename to round.ts, update all queries H
src/server/routers/track.ts Remove entirely, absorb into competition + specialAward H
src/server/routers/evaluation.ts stageId→roundId, add JuryGroup awareness M
src/server/routers/assignment.ts JuryGroup model, intent lifecycle H
src/server/routers/file.ts submissionWindowId, multi-round grouping M
src/server/routers/mentor.ts Workspace features, file promotion M
src/server/routers/award.ts competitionId, evaluationRoundId M
src/server/routers/audience-vote.ts stageId→roundId L
src/server/routers/coi.ts roundId awareness L
src/server/routers/notification.ts Stage event→round event mapping M
src/server/routers/cron.ts stageId→roundId in all scheduled jobs M
src/server/routers/deliberation.ts NEW — full deliberation router H
src/server/routers/result-lock.ts NEW — result lock/unlock procedures M
src/server/routers/jury-group.ts NEW — jury management procedures H
src/server/routers/submission-window.ts NEW — submission window CRUD M
Other 8 routers Indirect references (imports, type refs) L

Services (11 files, ~50 hours)

File Changes Risk
src/server/services/stage-engine.ts Rename to round-engine.ts, full rewrite H
src/server/services/stage-filtering.ts Rename to round-filtering.ts, roundId refs M
src/server/services/stage-assignment.ts Rename to round-assignment.ts, JuryGroup model H
src/server/services/stage-notifications.ts Rename to round-notifications.ts, event types M
src/server/services/live-control.ts stageId→roundId M
src/server/services/ai-filtering.ts stageId→roundId in AI context L
src/server/services/ai-assignment.ts JuryGroup awareness M
src/server/services/ai-evaluation-summary.ts roundId refs L
src/server/services/ai-tagging.ts stageId→roundId L
src/server/services/ai-award-eligibility.ts competitionId awareness L
src/server/services/anonymization.ts No changes expected L

Types & Libraries (6 files, ~32 hours)

File Changes Risk
src/types/pipeline-wizard.ts Complete rewrite → competition-wizard.ts H
src/types/wizard-config.ts Complete rewrite for round-based wizard H
src/lib/pipeline-defaults.ts Rename to competition-defaults.ts M
src/lib/pipeline-validation.ts Rename to competition-validation.ts M
src/lib/pipeline-conversions.ts Rename to competition-conversions.ts M
src/lib/stage-config-schema.ts Rename to round-config-schema.ts, Zod schemas M

Admin Pages (35+ files, ~52 hours)

File Pattern Count Changes Risk
src/app/(admin)/admin/pipelines/* 8 pages Rename to competitions/* H
src/app/(admin)/admin/stages/* 5 pages Rename to rounds/* H
Pipeline wizard components 6 Full rewrite for competition model H
Stage config components 8 Update to round-type configs M
Other admin components 10 Indirect references L

Jury Pages (16 files, ~18 hours)

File Pattern Count Changes Risk
src/app/(jury)/jury/stages/[stageId]/* 6 pages Rename to rounds/[roundId]/* M
Jury evaluation components 4 roundId, JuryGroup filtering M
Jury shared components 6 Breadcrumbs, timeline, badges L

Applicant Pages (8 files, ~12 hours)

File Pattern Count Changes Risk
src/app/(applicant)/applicant/pipeline/* 4 pages Rename to competition/* H
Applicant components 4 StageTimeline→RoundTimeline M

Tests (13+ files, ~45 hours)

File Changes Risk
tests/helpers.ts Update all factories M
tests/unit/stage-engine.test.ts Rename + update references M
tests/unit/stage-filtering.test.ts Rename + update M
tests/unit/stage-assignment.test.ts Rename + add JuryGroup tests M
tests/integration/pipeline-crud.test.ts Rename to competition-crud M
tests/integration/evaluation-flow.test.ts Round model, multi-round docs M
New test files (8 files) See 11-testing-and-qa.md H

Infrastructure (4 files, ~11 hours)

File Changes Risk
docker/docker-entrypoint.sh Migration flow update L
Cron job configs stageId→roundId in scheduled tasks M
Webhook event definitions Stage→Round event names M
Email templates Stage references in copy L

Totals

Category Files Est. Hours
Database & Schema 3 28
tRPC Routers 24 104
Services 11 50
Types & Libraries 6 32
Admin UI 35+ 52
Jury UI 16 18
Applicant UI 8 12
Tests 13+ 45
Infrastructure 4 11
Total 120+ ~352

Note: Conservative estimates for one developer. Parallel frontend/backend work reduces calendar time. Critical path: Schema → Services → Routers → UI.


Appendix: System Inventory — File-by-File Migration Map

Complete inventory of all files requiring changes, grouped by category with effort estimates.

Prisma & Database (1 file, HIGH effort)

File Changes Required Effort
prisma/schema.prisma 40+ model/enum updates, 17+ FK renames (stageId→roundId), add new enums, add new fields HIGH

tRPC Routers (24 files)

File Change Type Effort
src/server/routers/pipeline.ts Renamecompetition.ts + all procedures HIGH
src/server/routers/stage.ts Renameround.ts + all procedures HIGH
src/server/routers/track.ts Remove — logic absorbed into competition + specialAward HIGH
src/server/routers/evaluation.ts Update to use Round model, roundId params, JuryGroup queries HIGH
src/server/routers/assignment.ts Update to use JuryGroup model, roundId params HIGH
src/server/routers/file.ts Update listByProjectForStagelistByProjectForRound, multi-round grouping MEDIUM
src/server/routers/mentor.ts Add workspace features, file promotion procedures MEDIUM
src/server/routers/applicant.ts Update getMyDashboard (openStages→openRounds), pipeline view MEDIUM
src/server/routers/application.ts Update stage mode config → round-type-aware config MEDIUM
src/server/routers/live-control.ts Update stageId → roundId in cursor management MEDIUM
src/server/routers/award.ts Update to use competitionId, enhanced routing modes MEDIUM
src/server/routers/notification.ts Update event keys from stageId → roundId LOW
src/server/routers/analytics.ts Update stage references → round references LOW
src/server/routers/digest.ts Update stageId references LOW
src/server/routers/coi.ts Update stageId → roundId in COI declarations LOW
src/server/routers/audit.ts Update event type names (stage.* → round.*) LOW
New: src/server/routers/deliberation.ts New router for deliberation lifecycle HIGH
New: src/server/routers/result-lock.ts New router for result locking/unlocking MEDIUM
New: src/server/routers/jury-group.ts New router for jury group management MEDIUM
New: src/server/routers/submission-window.ts New router for submission window management MEDIUM

Services (11 files)

File Change Type Effort
src/server/services/stage-engine.ts Renameround-engine.ts, update all type references HIGH
src/server/services/stage-filtering.ts Renameround-filtering.ts, update to RoundType.FILTERING HIGH
src/server/services/stage-assignment.ts Renameround-assignment.ts, add JuryGroup-based assignment HIGH
src/server/services/stage-notifications.ts Renameround-notifications.ts, update event keys MEDIUM
src/server/services/live-control.ts Update stageId → roundId in cursor management MEDIUM
src/server/services/ai-filtering.ts Update stage references → round references LOW
src/server/services/ai-assignment.ts Update to use JuryGroup model MEDIUM
src/server/services/ai-evaluation-summary.ts Update stage references LOW
New: src/server/services/deliberation-engine.ts Deliberation lifecycle, vote aggregation, Borda count HIGH
New: src/server/services/result-lock.ts Lock/unlock with audit trail MEDIUM
New: src/server/services/mentor-workspace.ts Messaging, file management, promotion MEDIUM

Types & Libraries (6 files)

File Change Type Effort
src/types/pipeline-wizard.ts Rewritecompetition-wizard.ts with Competition/Round types HIGH
src/types/wizard-config.ts Rewrite → update all Stage→Round type references HIGH
src/lib/pipeline-defaults.ts Renamecompetition-defaults.ts, update all defaults MEDIUM
src/lib/pipeline-validation.ts Renamecompetition-validation.ts MEDIUM
src/lib/pipeline-conversions.ts Renamecompetition-conversions.ts MEDIUM
src/lib/stage-config-schema.ts Renameround-config-schema.ts, update to 7 typed Zod schemas HIGH

Admin Pages (13+ files)

File Change Type Effort
src/app/(admin)/pipelines/* Renamecompetitions/*, update all router calls HIGH
src/app/(admin)/pipelines/[id]/stages/* Renamecompetitions/[id]/rounds/* HIGH
src/app/(admin)/pipelines/[id]/tracks/* Remove — track UI eliminated MEDIUM
New: src/app/(admin)/competitions/[id]/juries/* New jury management section (3 pages) HIGH
New: src/app/(admin)/competitions/[id]/deliberation/* New deliberation management HIGH
New: src/app/(admin)/competitions/[id]/results/* New results & locks page MEDIUM
New: src/app/(admin)/competitions/[id]/submission-windows/* New submission window management MEDIUM

Jury Pages (6 files)

File Change Type Effort
src/app/(jury)/stages/[stageId]/* Renamerounds/[roundId]/* (6 routes) HIGH
src/app/(jury)/stages/[stageId]/evaluate/* Update to multi-round doc viewing, JuryGroup context HIGH
src/app/(jury)/stages/[stageId]/compare/* Update cross-round project comparison MEDIUM
New: src/app/(jury)/rounds/[roundId]/deliberation/* Juror deliberation voting interface HIGH

Applicant Pages (8 files)

File Change Type Effort
src/app/(applicant)/pipeline/* Renamecompetition/*, redesign progress visualization HIGH
src/app/(applicant)/pipeline/[stageId]/documents/* Rename → multi-round aware doc upload with read-only enforcement HIGH
src/app/(applicant)/pipeline/[stageId]/status/* Rename → round-based status with cross-round visibility MEDIUM

Shared Components (24+ files)

Component Change Effort
StageTimeline RenameRoundTimeline, update props from stageId to roundId MEDIUM
StageWindowBadge RenameRoundStatusBadge LOW
RequirementUploadSlot Update stageId prop → roundId + submissionWindowId LOW
PipelineWizard RenameCompetitionWizard, rewrite step flow HIGH
StageSidebar RenameRoundSidebar LOW
StageConfigEditor RenameRoundConfigEditor, update to typed Zod schemas HIGH
TrackSelector Remove LOW
Breadcrumb components Update Pipeline→Track→Stage to Competition→Round LOW

Infrastructure & Config (5 files)

File Change Type Effort
prisma/seed.ts Update all Pipeline/Stage creation to Competition/Round (300+ lines) HIGH
docker/docker-entrypoint.sh Update migration flow for new tables LOW
tests/helpers.ts Add new factory functions for Competition/Round/JuryGroup/etc. MEDIUM
.env.example Add any new feature flag env vars LOW
CLAUDE.md Update architecture documentation LOW

Background Jobs / Cron (3 files)

File Change Type Effort
src/server/cron/evaluation-reminders.ts Update stageId → roundId in deadline checks LOW
src/server/cron/digest-sender.ts Update stageId references in digest generation LOW
src/server/cron/ai-tagging.ts Update stageId → roundId in tagging jobs LOW

Test Files (12 files)

File Change Type Effort
tests/unit/stage-engine.test.ts Renameround-engine.test.ts, update all references MEDIUM
tests/unit/stage-filtering.test.ts Renameround-filtering.test.ts MEDIUM
tests/unit/stage-assignment.test.ts Rename, add JuryGroup-based tests, cap mode tests MEDIUM
tests/integration/pipeline-crud.test.ts Renamecompetition-crud.test.ts MEDIUM
tests/integration/evaluation-flow.test.ts Update to Round model, multi-round doc visibility MEDIUM
New: tests/unit/policy-resolution.test.ts 5-layer policy precedence tests HIGH
New: tests/unit/borda-count.test.ts Borda count aggregation tests LOW
New: tests/unit/deliberation-tally.test.ts Vote tallying for both modes MEDIUM
New: tests/unit/config-validation.test.ts All 7 Zod schema validation tests MEDIUM
New: tests/integration/deliberation-flow.test.ts Full deliberation lifecycle HIGH
New: tests/integration/mentor-workspace.test.ts Messaging, files, comments, promotion MEDIUM
New: tests/e2e/monaco-full-flow.test.ts Complete 8-round simulation HIGH

Webhook & Event Mapping

Old Event Type New Event Type
stage.transitioned round.transitioned
stage.opened round.opened
stage.closed round.closed
pipeline.created competition.created
pipeline.statusChanged competition.statusChanged
stage.assignmentCompleted round.assignmentCompleted
stage.evaluationCompleted round.evaluationCompleted
stage.filteringCompleted round.filteringCompleted
(new) deliberation.sessionCreated
(new) deliberation.voteSubmitted
(new) deliberation.resultLocked
(new) deliberation.resultUnlocked
(new) mentoring.filePromoted
(new) submission.windowOpened
(new) submission.windowClosed

Effort Summary

Category Files New Modified Removed Estimated Hours
Prisma & DB 1 0 1 0 1624
tRPC Routers 24 4 16 1 4060
Services 11 3 8 0 3040
Types & Libs 6 0 6 0 1624
Admin Pages 13+ 4 9 1 3040
Jury Pages 6+ 1 5 0 2030
Applicant Pages 8 0 8 0 1624
Components 24+ 0 20+ 2+ 2030
Infrastructure 5 0 5 0 812
Background Jobs 3 0 3 0 46
Tests 12 7 5 0 3040
Total 113+ 19 86+ 4+ 230330