Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# Mentoring & Document Lifecycle
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers two interconnected systems: (1) the multi-round document lifecycle that governs how submissions flow through the competition, and (2) the mentoring workspace that provides a collaboration layer for finalist teams with assigned mentors.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Document Lifecycle
|
||||
|
||||
### SubmissionWindow
|
||||
|
||||
Each round that requires document collection has an associated `SubmissionWindow`. A competition can have multiple windows (e.g., Round 1 application docs, Round 2 semifinal docs).
|
||||
|
||||
```prisma
|
||||
model SubmissionWindow {
|
||||
id String @id @default(cuid())
|
||||
competitionId String
|
||||
roundId String
|
||||
label String // "Round 1 Application Documents", "Round 2 Semifinal Documents"
|
||||
|
||||
opensAt DateTime
|
||||
closesAt DateTime
|
||||
deadlinePolicy DeadlinePolicy @default(HARD)
|
||||
gracePeriodMinutes Int? // only used with GRACE policy
|
||||
lockOnClose Boolean @default(true) // automatically lock submissions when window closes
|
||||
isLocked Boolean @default(false) // manual lock toggle (admin can lock before close)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
competition Competition @relation(...)
|
||||
round Round @relation(...)
|
||||
requirements SubmissionFileRequirement[]
|
||||
|
||||
@@index([competitionId])
|
||||
@@index([roundId])
|
||||
}
|
||||
```
|
||||
|
||||
### DeadlinePolicy
|
||||
|
||||
| Policy | Behavior |
|
||||
|--------|----------|
|
||||
| `HARD` | Submissions are cut off at `closesAt`. No uploads after the deadline. |
|
||||
| `FLAG` | Submissions after `closesAt` are accepted but marked as **Late**. Admin can see late status. |
|
||||
| `GRACE` | A grace period (`gracePeriodMinutes`) after `closesAt` during which submissions are still accepted without penalty. After grace, behaves as HARD. |
|
||||
|
||||
```prisma
|
||||
enum DeadlinePolicy {
|
||||
HARD
|
||||
FLAG
|
||||
GRACE
|
||||
}
|
||||
```
|
||||
|
||||
### SubmissionFileRequirement
|
||||
|
||||
Each window defines required file slots:
|
||||
|
||||
```prisma
|
||||
model SubmissionFileRequirement {
|
||||
id String @id @default(cuid())
|
||||
submissionWindowId String
|
||||
slotKey String // "executive_summary", "business_plan", "pitch_video"
|
||||
label String // "Executive Summary"
|
||||
description String? // guidance text for applicants
|
||||
required Boolean @default(true)
|
||||
maxFileSize Int @default(10485760) // 10MB default
|
||||
acceptedTypes String[] // ["application/pdf", "video/mp4"]
|
||||
sortOrder Int @default(0)
|
||||
|
||||
submissionWindow SubmissionWindow @relation(...)
|
||||
|
||||
@@unique([submissionWindowId, slotKey])
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Round Document Visibility
|
||||
|
||||
| Actor | Current Round Docs | Previous Round Docs |
|
||||
|-------|-------------------|-------------------|
|
||||
| **Applicant** | Can upload/edit until window closes | **Read-only** (view/download only) |
|
||||
| **Judge** | Sees current round docs in review | Sees previous round docs (clearly separated in UI) |
|
||||
| **Admin** | Full control (upload/remove/replace) | Full control (upload/remove/replace) |
|
||||
| **Mentor** | Sees within mentor workspace | Sees within mentor workspace |
|
||||
|
||||
When a submission window closes or a round advances:
|
||||
- Applicant's editing permissions for that window's docs are revoked
|
||||
- Docs remain visible for download
|
||||
- Judges in subsequent rounds can view all prior docs
|
||||
|
||||
### RoundSubmissionVisibility
|
||||
|
||||
Controls which prior round docs are visible to judges:
|
||||
|
||||
```prisma
|
||||
model RoundSubmissionVisibility {
|
||||
id String @id @default(cuid())
|
||||
roundId String // the round where the judge is reviewing
|
||||
visibleRoundId String // the round whose docs are visible
|
||||
separateInUi Boolean @default(true) // show in separate section
|
||||
|
||||
round Round @relation("reviewingRound", ...)
|
||||
visibleRound Round @relation("visibleRound", ...)
|
||||
|
||||
@@unique([roundId, visibleRoundId])
|
||||
}
|
||||
```
|
||||
|
||||
For example, Jury 2 (R5) would have visibility entries for R1 (Intake docs) and R4 (Semifinal docs), both with `separateInUi: true`.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Mentoring Workspace
|
||||
|
||||
### Purpose
|
||||
|
||||
Mentoring is NOT a judging stage. It's a collaboration layer for finalist teams who have requested mentoring. Mentors help teams polish their submissions before live finals.
|
||||
|
||||
### Who Gets Mentoring
|
||||
|
||||
- Only finalist teams (or whatever stage admin configures) that have "requested mentor" enabled
|
||||
- Mentor assignment works similarly to judge assignment (from a mentor pool)
|
||||
- Each mentored team gets one assigned mentor
|
||||
|
||||
### Workspace Features
|
||||
|
||||
The mentor-team workspace provides three capabilities:
|
||||
|
||||
#### 1. Messaging / Chat
|
||||
|
||||
```prisma
|
||||
model MentorMessage {
|
||||
id String @id @default(cuid())
|
||||
projectId String // the mentored project
|
||||
mentorId String // the assigned mentor
|
||||
senderId String // who sent the message
|
||||
senderRole MentorMessageRole
|
||||
content String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
project Project @relation(...)
|
||||
mentor User @relation("mentorMessages", ...)
|
||||
sender User @relation("sentMentorMessages", ...)
|
||||
|
||||
@@index([projectId, mentorId])
|
||||
}
|
||||
|
||||
enum MentorMessageRole {
|
||||
MENTOR
|
||||
APPLICANT
|
||||
ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
Messaging allows real-time communication between mentor and team. Admins can also send messages into the workspace.
|
||||
|
||||
#### 2. File Upload
|
||||
|
||||
```prisma
|
||||
model MentorFile {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
mentorId String
|
||||
uploadedById String
|
||||
uploaderRole MentorMessageRole
|
||||
fileName String
|
||||
fileKey String // MinIO storage key
|
||||
fileSize Int
|
||||
mimeType String
|
||||
description String?
|
||||
|
||||
// Promotion tracking
|
||||
promotedToSlot String? // slotKey if promoted to official submission
|
||||
promotedAt DateTime?
|
||||
promotedById String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(...)
|
||||
comments MentorFileComment[]
|
||||
|
||||
@@index([projectId, mentorId])
|
||||
}
|
||||
```
|
||||
|
||||
Both mentor and team can upload files. Files are stored in MinIO and accessed via pre-signed URLs.
|
||||
|
||||
#### 3. Threaded File Comments
|
||||
|
||||
```prisma
|
||||
model MentorFileComment {
|
||||
id String @id @default(cuid())
|
||||
fileId String
|
||||
authorId String
|
||||
authorRole MentorMessageRole
|
||||
content String @db.Text
|
||||
parentId String? // for threading
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
file MentorFile @relation(...)
|
||||
author User @relation(...)
|
||||
parent MentorFileComment? @relation("commentThread", ...)
|
||||
replies MentorFileComment[] @relation("commentThread")
|
||||
|
||||
@@index([fileId])
|
||||
}
|
||||
```
|
||||
|
||||
Comments are threaded (parentId for replies). Each comment is tied to a specific file.
|
||||
|
||||
### Privacy
|
||||
|
||||
All mentoring workspace content is **private by default**:
|
||||
- Visible to: the assigned mentor, the team members, and admins
|
||||
- NOT visible to: other teams, other mentors, judges, audience
|
||||
|
||||
### File Promotion to Official Submission
|
||||
|
||||
A mentoring file can be "promoted" to become an official submission for a required document slot in the active submission round.
|
||||
|
||||
**Example flow:**
|
||||
1. Team uploads "Business Plan Draft v3.pdf" to mentor workspace
|
||||
2. Mentor reviews and approves
|
||||
3. Team (or admin) marks it as the official "Business Plan" submission for Round 2
|
||||
4. System creates a `SubmissionPromotionEvent` with immutable provenance
|
||||
|
||||
```prisma
|
||||
model SubmissionPromotionEvent {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
roundId String
|
||||
slotKey String // the doc slot being filled
|
||||
sourceType PromotionSourceType
|
||||
sourceFileId String // MentorFile.id or admin-uploaded file ID
|
||||
promotedById String // who triggered the promotion
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
project Project @relation(...)
|
||||
round Round @relation(...)
|
||||
promotedBy User @relation(...)
|
||||
|
||||
@@index([projectId, roundId])
|
||||
}
|
||||
|
||||
enum PromotionSourceType {
|
||||
MENTOR_FILE // promoted from mentor workspace
|
||||
ADMIN_REPLACEMENT // admin replaced the file directly
|
||||
}
|
||||
```
|
||||
|
||||
**Promotion authority:**
|
||||
- **Team lead**: can promote their own mentor workspace files
|
||||
- **Admin**: can promote any file or directly replace with admin-uploaded file
|
||||
- **Mentor**: can promote IF admin enables this per competition (configurable)
|
||||
|
||||
**Provenance:** Every promotion records who promoted, when, from which source file, and the source type. This audit trail is immutable.
|
||||
|
||||
### MentoringConfig (Round Type Configuration)
|
||||
|
||||
```typescript
|
||||
const MentoringConfig = z.object({
|
||||
eligibility: z.enum(['FINALISTS_ONLY', 'SEMIFINALISTS_AND_ABOVE', 'CONFIGURABLE']),
|
||||
requireMentorRequest: z.boolean().default(true), // team must opt-in
|
||||
assignmentMethod: z.enum(['MANUAL', 'AI_SUGGESTED', 'AI_AUTO', 'ALGORITHM']),
|
||||
workspaceFeatures: z.object({
|
||||
messagingEnabled: z.boolean().default(true),
|
||||
fileUploadEnabled: z.boolean().default(true),
|
||||
fileCommentsEnabled: z.boolean().default(true),
|
||||
}),
|
||||
promotionTarget: z.string().optional(), // roundId where promoted files go
|
||||
allowMentorPromotion: z.boolean().default(false), // can mentors promote files?
|
||||
deadlinePolicy: z.nativeEnum(DeadlinePolicy).default('FLAG'),
|
||||
})
|
||||
```
|
||||
|
||||
### Mentor Dashboard
|
||||
|
||||
Mentors have a dedicated dashboard showing:
|
||||
- **Assigned teams**: list of all teams they're mentoring
|
||||
- **Per-team workspace**: click through to messaging, file exchange, comments
|
||||
- **Deadline tracking**: upcoming deadlines for the mentored teams
|
||||
- **Progress indicators**: which teams have submitted their required docs, which are pending
|
||||
- **File review queue**: files uploaded by teams that need mentor review
|
||||
|
||||
See [08-platform-integration-matrix.md](./08-platform-integration-matrix.md) for the full mentor page mapping.
|
||||
|
||||
---
|
||||
|
||||
## Admin Document Controls
|
||||
|
||||
Admins have full control over documents across all rounds:
|
||||
|
||||
| Action | Scope | Audit |
|
||||
|--------|-------|-------|
|
||||
| View any submission | All rounds, all projects | Read-only, no audit needed |
|
||||
| Upload file for a project | Any round, any slot | Logged with `sourceType: ADMIN_REPLACEMENT` |
|
||||
| Remove/replace a file | Any round, any slot | Logged with previous file reference |
|
||||
| Lock a submission window early | Specific window | Logged |
|
||||
| Extend a deadline | Specific window | Logged |
|
||||
| Promote a mentor file | Any project's workspace | Creates `SubmissionPromotionEvent` |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
- **R1 (Intake)**: First `SubmissionWindow` with R1 document requirements
|
||||
- **R4 (Semifinal Submission)**: Second `SubmissionWindow` with R2 requirements; R1 docs locked for applicants
|
||||
- **R5 (Jury 2)**: Judges see R1 + R2 docs clearly separated via `RoundSubmissionVisibility`
|
||||
- **R6 (Mentoring)**: Mentor workspace active, file promotion targets the active submission window
|
||||
- **R7 (Live Finals)**: Jury 3 sees all submitted docs from all rounds
|
||||
|
||||
See [03-competition-flow.md](./03-competition-flow.md) for the complete round-by-round specification.
|
||||
Reference in New Issue
Block a user