Skip to main content

exo_consensus/
round.rs

1// Copyright 2026 Exochain Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at:
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17use 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/// Structured deterministic response from one panel model.
28#[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/// Structured deterministic devil's advocate review.
36#[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/// A single position submitted by a model in a round.
44#[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/// A complete round of deliberation.
57#[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    /// Hashes the round deterministically.
70    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}