1use std::collections::BTreeMap;
18
19use exo_core::{
20 hash::hash_structured,
21 types::{Hash256, Timestamp},
22};
23use serde::{Deserialize, Serialize};
24
25use crate::error::{ConsensusError, Result};
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29pub struct ModelDeliberationResponse {
30 pub position_text: String,
31 pub key_claims: Vec<String>,
32 pub confidence_bps: u64,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37pub struct DevilAdvocateReview {
38 pub review_text: String,
39 pub serious_objection: bool,
40 pub reasons: Vec<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ModelPosition {
46 pub model_id: String,
47 pub round: u32,
48 pub position_hash: Hash256,
49 pub position_text: String,
50 pub key_claims: Vec<String>,
51 pub confidence_bps: u64,
52 pub submitted_at: Timestamp,
53 pub revealed_at: Option<Timestamp>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DeliberationRound {
59 pub round_number: u32,
60 pub question: String,
61 pub positions: BTreeMap<String, ModelPosition>,
62 pub synthesis: Option<String>,
63 pub convergence_score_bps: u64,
64 pub devil_advocate_review: Option<DevilAdvocateReview>,
65 pub round_hash: Hash256,
66}
67
68impl DeliberationRound {
69 pub fn compute_hash(&self) -> Result<Hash256> {
71 hash_round(self)
72 }
73}
74
75pub fn hash_round(round: &DeliberationRound) -> Result<Hash256> {
76 #[derive(Serialize)]
77 struct HashInput<'a> {
78 pub domain: &'static str,
79 pub schema_version: &'static str,
80 pub round_number: u32,
81 pub question: &'a str,
82 pub positions: &'a BTreeMap<String, ModelPosition>,
83 pub synthesis: &'a Option<String>,
84 pub convergence_score_bps: u64,
85 pub devil_advocate_review: &'a Option<DevilAdvocateReview>,
86 }
87
88 let input = HashInput {
89 domain: "exo.consensus.deliberation_round.v1",
90 schema_version: "1",
91 round_number: round.round_number,
92 question: &round.question,
93 positions: &round.positions,
94 synthesis: &round.synthesis,
95 convergence_score_bps: round.convergence_score_bps,
96 devil_advocate_review: &round.devil_advocate_review,
97 };
98
99 hash_structured(&input).map_err(|source| ConsensusError::HashSerialization {
100 context: "deliberation round",
101 source,
102 })
103}