Skip to main content

heddle_client/grpc_hosted/
state_review.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Hosted `StateReviewService` client methods.
3//!
4//! Review signatures are server-minted via caller-authenticated, PoP-signed
5//! RPCs (weft#549) — the object pack rejects client-pushed `ReviewSignatures`
6//! attachments, so `SignState`/`RecordVerdict` are the ONLY channel by which a
7//! signature reaches the hosted server. `heddle review sync` (push path) replays
8//! locally-recorded review signatures over these methods.
9
10use grpc::heddle::api::v1alpha1::{
11    ListSignaturesRequest, ListSignaturesResponse, RecordVerdictRequest, RecordVerdictResponse,
12    ReviewKind, ReviewScope, SignStateRequest, SignStateResponse, Verdict,
13};
14use objects::object::StateId;
15use tonic::Request;
16use wire::ProtocolError;
17
18use super::{HostedGrpcClient, helpers::status_to_protocol_error, operation_id::ClientOperationId};
19
20const SIGN_STATE: &str = "heddle.api.v1alpha1.StateReviewService/SignState";
21const RECORD_VERDICT: &str = "heddle.api.v1alpha1.StateReviewService/RecordVerdict";
22
23impl HostedGrpcClient {
24    /// Mint a hosted review signature over `state_id`. `signature`/`public_key`
25    /// are the raw bytes the caller already computed over the canonical
26    /// `signing_payload` (byte-identical to the server's reconstruction), so the
27    /// server verifies the exact same signature the local `review sign` wrote.
28    #[allow(clippy::too_many_arguments)]
29    pub async fn sign_state(
30        &mut self,
31        repo_path: &str,
32        state_id: &StateId,
33        kind: ReviewKind,
34        scope: ReviewScope,
35        justification: &str,
36        algorithm: &str,
37        public_key: Vec<u8>,
38        signature: Vec<u8>,
39        signed_at_unix: i64,
40        client_operation_id: String,
41    ) -> Result<SignStateResponse, ProtocolError> {
42        let operation_id = ClientOperationId::for_required_method(SIGN_STATE, client_operation_id)?;
43        let mut request = Request::new(SignStateRequest {
44            repo_path: super::helpers::repository_ref(repo_path),
45            state_id: super::helpers::proto_state_id(*state_id),
46            kind: kind as i32,
47            scope: Some(scope),
48            justification: justification.to_string(),
49            algorithm: algorithm.to_string(),
50            public_key,
51            signature,
52            signed_at: Some(prost_types::Timestamp {
53                seconds: signed_at_unix,
54                nanos: 0,
55            }),
56            client_operation_id: operation_id.to_wire(),
57        });
58        self.apply_signed_auth(
59            &mut request,
60            "/heddle.api.v1alpha1.StateReviewService/SignState",
61        )?;
62        self.review
63            .sign_state(request)
64            .await
65            .map_err(status_to_protocol_error)
66            .map(|response| response.into_inner())
67    }
68
69    /// Record a signed SIGN/HOLD/REJECT reviewer verdict over `state_id`.
70    /// Decoupled from status — returns the same state. Same signing model as
71    /// [`Self::sign_state`].
72    #[allow(clippy::too_many_arguments)]
73    pub async fn record_verdict(
74        &mut self,
75        repo_path: &str,
76        state_id: &StateId,
77        verdict: Verdict,
78        scope: ReviewScope,
79        reason: &str,
80        algorithm: &str,
81        public_key: Vec<u8>,
82        signature: Vec<u8>,
83        signed_at_unix: i64,
84        client_operation_id: String,
85    ) -> Result<RecordVerdictResponse, ProtocolError> {
86        let operation_id =
87            ClientOperationId::for_required_method(RECORD_VERDICT, client_operation_id)?;
88        let mut request = Request::new(RecordVerdictRequest {
89            repo_path: super::helpers::repository_ref(repo_path),
90            state_id: super::helpers::proto_state_id(*state_id),
91            verdict: verdict as i32,
92            scope: Some(scope),
93            reason: reason.to_string(),
94            algorithm: algorithm.to_string(),
95            public_key,
96            signature,
97            signed_at: Some(prost_types::Timestamp {
98                seconds: signed_at_unix,
99                nanos: 0,
100            }),
101            client_operation_id: operation_id.to_wire(),
102        });
103        self.apply_signed_auth(
104            &mut request,
105            "/heddle.api.v1alpha1.StateReviewService/RecordVerdict",
106        )?;
107        self.review
108            .record_verdict(request)
109            .await
110            .map_err(status_to_protocol_error)
111            .map(|response| response.into_inner())
112    }
113
114    /// List hosted review signatures recorded against `state_id`. Read-only,
115    /// authenticated (unsigned tier).
116    pub async fn list_review_signatures(
117        &mut self,
118        repo_path: &str,
119        state_id: &StateId,
120    ) -> Result<ListSignaturesResponse, ProtocolError> {
121        let mut request = Request::new(ListSignaturesRequest {
122            repo_path: super::helpers::repository_ref(repo_path),
123            state_id: super::helpers::proto_state_id(*state_id),
124        });
125        self.apply_signed_auth(
126            &mut request,
127            "/heddle.api.v1alpha1.StateReviewService/ListSignatures",
128        )?;
129        self.review
130            .list_signatures(request)
131            .await
132            .map_err(status_to_protocol_error)
133            .map(|response| response.into_inner())
134    }
135}