Skip to main content

jar_genesis/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// A review verdict: merge or notMerge.
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub enum Verdict {
6    #[serde(rename = "merge")]
7    Merge,
8    #[serde(rename = "notMerge")]
9    NotMerge,
10}
11
12/// A single review embedded in a SignedCommit.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct EmbeddedReview {
16    pub reviewer: String,
17    pub difficulty_ranking: Vec<String>,
18    pub novelty_ranking: Vec<String>,
19    pub design_quality_ranking: Vec<String>,
20    pub verdict: Verdict,
21}
22
23/// A meta-review (thumbs up/down on another reviewer's review).
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct MetaReview {
27    pub meta_reviewer: String,
28    pub target_reviewer: String,
29    pub approve: bool,
30}
31
32/// A signed commit: the full input to genesis_evaluate.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct SignedCommit {
36    pub id: String,
37    pub pr_id: u64,
38    pub author: String,
39    pub merge_epoch: u64,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub pr_created_at: Option<u64>,
42    pub comparison_targets: Vec<String>,
43    pub reviews: Vec<EmbeddedReview>,
44    pub meta_reviews: Vec<MetaReview>,
45    pub founder_override: bool,
46}
47
48/// Score for a commit (output of genesis_evaluate).
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CommitScore {
52    pub difficulty: u64,
53    pub novelty: u64,
54    pub design_quality: u64,
55}
56
57/// A scored commit index (output of genesis_evaluate, stored in cache and trailers).
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct CommitIndex {
61    pub commit_hash: String,
62    pub epoch: u64,
63    pub score: CommitScore,
64    pub contributor: String,
65    pub weight_delta: u64,
66    pub reviewers: Vec<String>,
67    pub meta_reviews: Vec<MetaReview>,
68    pub merge_votes: Vec<String>,
69    pub reject_votes: Vec<String>,
70    pub founder_override: bool,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub warnings: Option<Vec<String>>,
73}
74
75/// Output of genesis_check_merge.
76#[derive(Debug, Clone, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct MergeReadiness {
79    pub ready: bool,
80    pub merge_weight: u64,
81    pub reject_weight: u64,
82    pub total_weight: u64,
83}
84
85/// Output of genesis_select_targets.
86#[derive(Debug, Clone, Deserialize)]
87pub struct SelectTargetsOutput {
88    pub targets: Vec<String>,
89}
90
91/// Output of genesis_ranking.
92#[derive(Debug, Clone, Deserialize)]
93pub struct RankingOutput {
94    pub ranking: Vec<String>,
95}
96
97/// Output of genesis_validate.
98#[derive(Debug, Clone, Deserialize)]
99pub struct ValidateOutput {
100    pub valid: bool,
101    pub errors: Vec<String>,
102}
103
104/// Collected reviews from a PR (output of review::collect).
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct CollectedReviews {
108    pub reviews: Vec<EmbeddedReview>,
109    pub meta_reviews: Vec<MetaReview>,
110    pub warnings: Vec<String>,
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_verdict_serde_roundtrip() {
119        let merge = serde_json::to_string(&Verdict::Merge).unwrap();
120        assert_eq!(merge, "\"merge\"");
121        let not_merge = serde_json::to_string(&Verdict::NotMerge).unwrap();
122        assert_eq!(not_merge, "\"notMerge\"");
123
124        let parsed: Verdict = serde_json::from_str("\"merge\"").unwrap();
125        assert_eq!(parsed, Verdict::Merge);
126        let parsed: Verdict = serde_json::from_str("\"notMerge\"").unwrap();
127        assert_eq!(parsed, Verdict::NotMerge);
128    }
129
130    #[test]
131    fn test_commit_index_camel_case() {
132        let json = r#"{
133            "commitHash": "abc123",
134            "epoch": 1000,
135            "score": {"difficulty": 85, "novelty": 100, "designQuality": 85},
136            "contributor": "alice",
137            "weightDelta": 88,
138            "reviewers": ["bob"],
139            "metaReviews": [],
140            "mergeVotes": ["bob"],
141            "rejectVotes": [],
142            "founderOverride": false
143        }"#;
144        let idx: CommitIndex = serde_json::from_str(json).unwrap();
145        assert_eq!(idx.commit_hash, "abc123");
146        assert_eq!(idx.score.design_quality, 85);
147        assert_eq!(idx.weight_delta, 88);
148        assert!(!idx.founder_override);
149        assert!(idx.warnings.is_none());
150    }
151
152    #[test]
153    fn test_commit_index_with_warnings() {
154        let json = r#"{
155            "commitHash": "abc",
156            "epoch": 1000,
157            "score": {"difficulty": 0, "novelty": 0, "designQuality": 0},
158            "contributor": "alice",
159            "weightDelta": 0,
160            "reviewers": [],
161            "metaReviews": [],
162            "mergeVotes": [],
163            "rejectVotes": [],
164            "founderOverride": false,
165            "warnings": ["some warning"]
166        }"#;
167        let idx: CommitIndex = serde_json::from_str(json).unwrap();
168        assert_eq!(idx.warnings.as_ref().unwrap().len(), 1);
169    }
170
171    #[test]
172    fn test_commit_index_without_warnings_backward_compat() {
173        // Old cache entries don't have the warnings field
174        let json = r#"{
175            "commitHash": "abc",
176            "epoch": 1000,
177            "score": {"difficulty": 0, "novelty": 0, "designQuality": 0},
178            "contributor": "alice",
179            "weightDelta": 0,
180            "reviewers": [],
181            "metaReviews": [],
182            "mergeVotes": [],
183            "rejectVotes": [],
184            "founderOverride": false
185        }"#;
186        let idx: CommitIndex = serde_json::from_str(json).unwrap();
187        assert!(idx.warnings.is_none());
188    }
189
190    #[test]
191    fn test_embedded_review_serde() {
192        let review = EmbeddedReview {
193            reviewer: "alice".to_string(),
194            difficulty_ranking: vec!["abc".to_string(), "def".to_string()],
195            novelty_ranking: vec!["def".to_string(), "abc".to_string()],
196            design_quality_ranking: vec!["abc".to_string(), "def".to_string()],
197            verdict: Verdict::Merge,
198        };
199        let json = serde_json::to_string(&review).unwrap();
200        assert!(json.contains("difficultyRanking"));
201        assert!(json.contains("designQualityRanking"));
202        let parsed: EmbeddedReview = serde_json::from_str(&json).unwrap();
203        assert_eq!(parsed.reviewer, "alice");
204    }
205
206    #[test]
207    fn test_signed_commit_serde() {
208        let commit = SignedCommit {
209            id: "abc123".to_string(),
210            pr_id: 42,
211            author: "alice".to_string(),
212            merge_epoch: 1000,
213            pr_created_at: Some(900),
214            comparison_targets: vec!["target1".to_string()],
215            reviews: vec![],
216            meta_reviews: vec![],
217            founder_override: false,
218        };
219        let json = serde_json::to_string(&commit).unwrap();
220        assert!(json.contains("prId"));
221        assert!(json.contains("mergeEpoch"));
222        assert!(json.contains("prCreatedAt"));
223        assert!(json.contains("founderOverride"));
224    }
225
226    #[test]
227    fn test_signed_commit_without_pr_created_at() {
228        // Legacy commits might not have prCreatedAt
229        let json = r#"{
230            "id": "abc",
231            "prId": 1,
232            "author": "alice",
233            "mergeEpoch": 1000,
234            "comparisonTargets": [],
235            "reviews": [],
236            "metaReviews": [],
237            "founderOverride": false
238        }"#;
239        let commit: SignedCommit = serde_json::from_str(json).unwrap();
240        assert!(commit.pr_created_at.is_none());
241    }
242
243    #[test]
244    fn test_merge_readiness_serde() {
245        let json =
246            r#"{"ready": true, "mergeWeight": 1000, "rejectWeight": 0, "totalWeight": 1000}"#;
247        let readiness: MergeReadiness = serde_json::from_str(json).unwrap();
248        assert!(readiness.ready);
249        assert_eq!(readiness.merge_weight, 1000);
250    }
251}