318 lines
10 KiB
Markdown
318 lines
10 KiB
Markdown
# 04. Unified Domain Model And Config Contracts
|
|
|
|
## 1) Objectives
|
|
This target model turns the Monaco flow into strict, reusable contracts while preserving the current pipeline infrastructure.
|
|
|
|
Goals:
|
|
1. Minimize schema churn where current models are already correct.
|
|
2. Remove ambiguity for jury identity, assignment policy, round bundles, mentoring promotion, and final lock.
|
|
3. Keep customization through explicit policy layers, not ad hoc conditionals.
|
|
|
|
## 2) Proposed Domain Additions
|
|
|
|
## 2.1 Stage Purpose Contract
|
|
|
|
### Add To `Stage`
|
|
- `purposeKey` (enum/string, required after migration)
|
|
|
|
Proposed purpose enum values:
|
|
- `submission_r1_intake`
|
|
- `eligibility_filter`
|
|
- `jury1_evaluation`
|
|
- `submission_r2_intake`
|
|
- `jury2_evaluation`
|
|
- `jury3_live_finals`
|
|
- `final_confirmation`
|
|
- `results_publication`
|
|
- `award_evaluation`
|
|
- `award_results`
|
|
|
|
Rule:
|
|
- `StageType` remains coarse technical type.
|
|
- `purposeKey` controls business behavior and policy validation.
|
|
|
|
## 2.2 Jury As First-Class Entity
|
|
|
|
### New `Jury`
|
|
Fields:
|
|
- `id`, `programId`, `purposeKey`, `code`, `displayLabel`, `kind` (`MAIN`, `AWARD`), `isActive`
|
|
|
|
Examples:
|
|
- `code=main-semifinal`, `displayLabel=Technical Semi-Final Jury`
|
|
- `code=main-finalist`, `displayLabel=Grand Jury`
|
|
- `code=award-blue-innovation`, `displayLabel=Blue Innovation Prize Jury`
|
|
|
|
### New `JuryMembership`
|
|
Fields:
|
|
- `juryId`, `userId`, `roleInJury` (`CHAIR`, `MEMBER`), `activeFrom`, `activeTo`, `status`
|
|
|
|
### New `JuryStageBinding`
|
|
Fields:
|
|
- `juryId`, `stageId`, `isPrimary`, `permissionsJson`
|
|
|
|
Use:
|
|
- controls which juries are allowed to evaluate/vote in each stage
|
|
- allows overlap across juries and awards
|
|
|
|
## 2.3 Assignment Policy Contract
|
|
|
|
### New `AssignmentPolicy` (stage + jury scoped)
|
|
Fields:
|
|
- `stageId`
|
|
- `juryId`
|
|
- `requiredReviews`
|
|
- `defaultCap`
|
|
- `defaultCapMode` (`HARD`, `SOFT`)
|
|
- `softBuffer` (default 10, configurable)
|
|
- `categoryBiasPolicyJson` (startup/concept mix preference weights; non-deterministic)
|
|
- `overflowPolicy` (`manual_queue`, `expand_pool`, `reduce_reviews`)
|
|
- `isActive`
|
|
|
|
### New `JudgePolicyOverride`
|
|
Fields:
|
|
- `assignmentPolicyId`
|
|
- `userId`
|
|
- `cap`, `capMode`, `softBuffer`
|
|
- `startupBiasWeight`, `conceptBiasWeight`
|
|
- `biasDisclosureAcceptedAt` (onboarding acknowledgement that bias is suggestive only)
|
|
- `source` (`ADMIN_SET`, `JUDGE_ONBOARDING`)
|
|
|
|
### New `AssignmentIntent`
|
|
Purpose:
|
|
- records desired assignments created before assignment materialization (e.g., from member invite page)
|
|
|
|
Fields:
|
|
- `stageId`, `juryId`, `userId`, `projectId`, `intentSource`, `status`
|
|
|
|
### New `AssignmentException`
|
|
Purpose:
|
|
- explicit record for manual over-cap assignments
|
|
|
|
Fields:
|
|
- `assignmentId`, `policyId`, `exceptionType`, `reasonCode`, `reasonText`, `approvedBy`
|
|
|
|
## 2.4 Submission Round Bundle Contract
|
|
|
|
### New `SubmissionRound`
|
|
Fields:
|
|
- `id`, `stageId`, `roundKey`, `name`, `openAt`, `closeAt`, `latePolicy`, `lateGraceHours`, `editabilityPolicy`
|
|
|
|
### Evolve `FileRequirement`
|
|
Add:
|
|
- `submissionRoundId` (nullable transitional, required after migration)
|
|
- `slotKey` (stable requirement slot identifier)
|
|
|
|
### Evolve `ProjectFile`
|
|
Add:
|
|
- `submissionRoundId`
|
|
- `submissionSlotKey`
|
|
- `sourceType` (`DIRECT_UPLOAD`, `MENTOR_PROMOTION`, `ADMIN_REPLACEMENT`)
|
|
- `sourceReferenceId` (e.g., mentor file id)
|
|
- `isOfficial`
|
|
|
|
### New `SubmissionBundleState`
|
|
Fields:
|
|
- `projectId`, `submissionRoundId`, `status` (`DRAFT`, `SUBMITTED`, `LOCKED`), `lockedAt`, `lockedBy`
|
|
|
|
## 2.5 Mentoring Collaboration + Promotion
|
|
|
|
### New `MentorWorkspaceFile`
|
|
Fields:
|
|
- `projectId`, `mentorId` (nullable for team upload), file storage metadata, visibility, status
|
|
|
|
### New `MentorWorkspaceComment`
|
|
Fields:
|
|
- `workspaceFileId`, `authorId`, `threadKey`, `content`, timestamps
|
|
|
|
### New `SubmissionPromotionEvent`
|
|
Fields:
|
|
- `projectId`, `workspaceFileId`, `targetSubmissionRoundId`, `targetSlotKey`
|
|
- `promotedById`, `promotedAt`
|
|
- `resultProjectFileId`
|
|
- `approvalState` (if mentor/admin approvals required)
|
|
|
|
Invariant:
|
|
- promotion always creates immutable provenance link from mentoring artifact to official submission slot.
|
|
- promotion authority is limited to team lead and admin.
|
|
|
|
## 2.6 Special Award Governance Contract
|
|
|
|
### Extend `SpecialAward`
|
|
Add:
|
|
- `participationMode` (`SEPARATE_POOL`, `DUAL_TRACK`)
|
|
- `routingBehavior` (`PULL_FROM_MAIN`, `KEEP_IN_MAIN`)
|
|
- `routingConfirmationMode` (`AUTO`, `ADMIN_CONFIRMED`) // Monaco default: `ADMIN_CONFIRMED` for pull-out
|
|
- `requiresDedicatedJury` (bool)
|
|
- `winnerDecisionMode` (`JURY_CONFIRMATION`, `SINGLE_JUDGE_DECIDES`)
|
|
- `singleJudgeUserId` (nullable, required when `winnerDecisionMode=SINGLE_JUDGE_DECIDES`)
|
|
- `winnerCandidateSource` (`ELIGIBILITY_REMAINING_POOL`, `CUSTOM_SHORTLIST`) // Monaco single-judge awards use eligibility remaining pool
|
|
- `submissionRequirementMode` (`REUSE_MAIN`, `CUSTOM`)
|
|
|
|
### New `AwardStageBinding`
|
|
Fields:
|
|
- `awardId`, `filterStageId`, `evaluationStageId`, `resultStageId`
|
|
|
|
## 2.7 Final Confirmation Contract
|
|
|
|
### New `FinalConfirmationSession`
|
|
Fields:
|
|
- `stageId` (final confirmation stage)
|
|
- `status` (`OPEN`, `PENDING_ADMIN_APPROVAL`, `FINALIZED`, `CANCELLED`)
|
|
- `decisionRule` (`UNANIMOUS`, `SUPERMAJORITY`, `SIMPLE_MAJORITY`, `SINGLE_JUDGE_DECIDES`)
|
|
- `quorumPolicy` (`ACTIVE_MEMBERS_ONLY`, `ALLOW_REPLACEMENT`)
|
|
- `requiredApprovalsCount` (resolved from active quorum after absence/replacement decisions)
|
|
- `scope` (`CATEGORY`, `AWARD`)
|
|
- `isAdminOverridden` (bool)
|
|
- `overrideReasonCode`, `overrideReasonText`, `overriddenByAdminId`, `overriddenAt`
|
|
- `finalizedByAdminId`, `finalizedAt`
|
|
|
|
### New `FinalConfirmationParticipant`
|
|
Fields:
|
|
- `sessionId`, `juryMemberId`
|
|
- `status` (`REQUIRED`, `ABSENT_EXCUSED`, `REPLACED`, `REPLACEMENT_ACTIVE`)
|
|
- `replacedByJuryMemberId` (nullable)
|
|
- `absenceReasonCode`, `absenceReasonText`
|
|
- `updatedByAdminId`, `updatedAt`
|
|
|
|
### New `FinalConfirmationVote`
|
|
Fields:
|
|
- `sessionId`, `juryMemberId`, `projectId/category/awardScope`, `decision`, `comment`
|
|
|
|
### New `ResultLock`
|
|
Fields:
|
|
- `sessionId`, `programId`, `lockVersion`, `lockedAt`, `lockedBy`, `snapshotJson`
|
|
|
|
### New `ResultUnlockEvent`
|
|
Fields:
|
|
- `resultLockId`, `programId`
|
|
- `unlockedBySuperAdminId`, `unlockedAt`
|
|
- `reasonCode`, `reasonText`
|
|
- `relockVersion` (nullable until relock)
|
|
|
|
Invariant:
|
|
- once `ResultLock` is created, winner outputs are immutable except via explicit super-admin unlock workflow with mandatory audit reason.
|
|
|
|
## 3) Policy Precedence Model
|
|
|
|
Order of precedence (highest to lowest):
|
|
1. Explicit admin override action (with reason)
|
|
2. Per-user override policy (e.g., judge cap override)
|
|
3. Stage+jury policy
|
|
4. Program-level default policy
|
|
5. System default
|
|
|
|
All runtime evaluators must return:
|
|
- resolved value
|
|
- source layer
|
|
- explanation payload for audit/debug UI
|
|
|
|
## 4) Access Contract (Role + Context)
|
|
|
|
Replace scattered checks with `resolveAccess(user, context)` where context includes:
|
|
- `programId`
|
|
- `trackId`
|
|
- `stageId`
|
|
- `stagePurposeKey`
|
|
- `juryId` (if applicable)
|
|
- `projectId` (if applicable)
|
|
- `submissionRoundId` (if applicable)
|
|
|
|
Returns typed permissions:
|
|
- `canViewProject`
|
|
- `canUploadSubmissionSlot`
|
|
- `canViewPreviousRoundDocs`
|
|
- `canAssignProjects`
|
|
- `canVoteLive`
|
|
- `canConfirmFinalWinners`
|
|
- etc.
|
|
|
|
## 5) Assignment Engine Contract
|
|
|
|
Input:
|
|
- projects eligible in stage
|
|
- jury members bound to stage
|
|
- resolved per-judge policy (cap mode, cap, buffer, category-bias preference)
|
|
- required reviews
|
|
- COI and availability constraints
|
|
|
|
Required behavior:
|
|
1. satisfy hard-cap constraints strictly
|
|
2. satisfy soft-cap targets before using buffer
|
|
3. apply startup/concept distribution as a soft scoring bias (never a strict blocker)
|
|
4. when all soft-cap members hit cap+buffer, place remainder in manual queue
|
|
5. never silently drop unassigned projects
|
|
|
|
Output:
|
|
- assignment set
|
|
- unassigned queue with explicit reasons
|
|
- policy-compliance summary
|
|
|
|
## 6) Document Contract
|
|
|
|
For every stage with submission behavior:
|
|
- active `SubmissionRound` must exist
|
|
- all required slots represented by `FileRequirement.slotKey`
|
|
- applicant write rights only for active round if open
|
|
- previous round slots read-only in applicant scope
|
|
- judges see current + previous according to stage policy
|
|
- admins full mutate rights with replacement provenance
|
|
|
|
## 7) Invite + Onboarding Contract
|
|
|
|
## 7.1 Admin Member Invite
|
|
- Invitations create `User` + optional `JuryMembership`.
|
|
- Jury selection uses program-defined custom jury labels, while binding to explicit stage-purpose contracts.
|
|
- Pre-assignment supports both modes:
|
|
- intent-first mode via `AssignmentIntent` (default recommended)
|
|
- direct assignment mode via `Assignment` (explicitly enabled and policy-validated)
|
|
|
|
## 7.2 Team Invite
|
|
- Team lead invites create/attach `TeamMember`
|
|
- invite token acceptance path remains, but post-accept routing resolves to proper applicant/team context
|
|
|
|
## 7.3 Accept Invite Routing
|
|
After acceptance:
|
|
- if user has jury memberships pending onboarding -> jury onboarding journey
|
|
- if mentor -> mentor onboarding
|
|
- if applicant team member only -> applicant team dashboard
|
|
|
|
## 8) Event/Audit Contract
|
|
|
|
Every significant workflow event emits:
|
|
- business event type (`assignment.generated`, `final_confirmation.finalized`, etc.)
|
|
- actor
|
|
- entity scope
|
|
- policy snapshot
|
|
- before/after state where relevant
|
|
|
|
Override events must include:
|
|
- reason code
|
|
- reason text
|
|
- impacted policy key
|
|
- reviewer chain (if applicable)
|
|
|
|
## 9) API Contract Evolution (Router-Level)
|
|
|
|
## 9.1 Keep and Evolve
|
|
- Keep existing routers (`pipeline`, `stage`, `assignment`, `evaluation`, `file`, `mentor`, `live-voting`, `decision`, `user`, etc.)
|
|
- Add new endpoints for purpose/policy/jury/final confirmation and deprecate ambiguous patterns progressively
|
|
|
|
## 9.2 New Endpoint Families
|
|
- `jury.*`: CRUD memberships/bindings
|
|
- `assignmentPolicy.*`: configure and inspect effective policy
|
|
- `submissionRound.*`: lifecycle and bundle state
|
|
- `mentorWorkspace.*`: files/comments/promotion
|
|
- `finalConfirmation.*`: create/collect/finalize lock, handle quorum fallback, and manage juror replacement/absence
|
|
- `resultUnlock.*`: super-admin unlock/relock workflow with audit capture
|
|
- `access.*`: debug effective permissions (admin-only)
|
|
|
|
## 10) Migration Safety Rules
|
|
1. Additive migrations first.
|
|
2. Backfill `purposeKey` and policy references from existing configs.
|
|
3. Dual-read / single-write transition windows where needed.
|
|
4. Feature flags for critical runtime path switches.
|
|
5. No silent behavior changes in production without compatibility mode.
|
|
|
|
## 11) End State
|
|
A coherent operating model where all functions, pages, and APIs are consistent with Monaco flow and each key behavior is explainable, testable, and auditable.
|