Skip to main content

trace_share_core/
consent.rs

1use anyhow::{Result, bail};
2use chrono::Utc;
3use serde::{Deserialize, Serialize};
4
5use crate::state::StateStore;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ConsentState {
9    pub accepted_at: String,
10    pub consent_version: String,
11    pub license: String,
12    pub public_searchable: bool,
13    pub trainable: bool,
14    pub ack_sanitization: bool,
15    pub ack_public_search: bool,
16    pub ack_training_release: bool,
17}
18
19pub fn allowed_license(license: &str) -> bool {
20    matches!(license, "CC0-1.0" | "CC-BY-4.0")
21}
22
23pub fn init_consent(
24    store: &StateStore,
25    license: &str,
26    consent_version: &str,
27) -> Result<ConsentState> {
28    if !allowed_license(license) {
29        bail!("license must be one of: CC0-1.0, CC-BY-4.0");
30    }
31
32    let state = ConsentState {
33        accepted_at: Utc::now().to_rfc3339(),
34        consent_version: consent_version.to_string(),
35        license: license.to_string(),
36        public_searchable: true,
37        trainable: true,
38        ack_sanitization: true,
39        ack_public_search: true,
40        ack_training_release: true,
41    };
42    store.upsert_consent_state(&state)?;
43    Ok(state)
44}
45
46pub fn require_consent(store: &StateStore) -> Result<ConsentState> {
47    let Some(state) = store.consent_state()? else {
48        bail!(
49            "consent not initialized. run: trace-share consent init --license <CC0-1.0|CC-BY-4.0>"
50        );
51    };
52    if !state.public_searchable || !state.trainable {
53        bail!("consent state does not allow searchable+trainable uploads");
54    }
55    if !allowed_license(&state.license) {
56        bail!("consent license invalid: {}", state.license);
57    }
58    if !(state.ack_sanitization && state.ack_public_search && state.ack_training_release) {
59        bail!("consent acknowledgements incomplete");
60    }
61    Ok(state)
62}