Skip to main content

riffdb_types/
secret.rs

1//! The stable redaction marker for secret-classified fields (ADR-0118).
2//!
3//! The value-bearing wrapper (`SecretValue`) and the reveal authority live in
4//! `riffdb-policy`, because reveal authority is mintable only from a policy
5//! evaluation's sealed [`FieldMask`](https://docs.rs) obligation. This module
6//! carries only the value-FREE vocabulary shared by display surfaces: the
7//! marker text and the withheld-field descriptor.
8//!
9//! The classification is display-and-visibility metadata only: durable
10//! storage, backups, exports, and changelog frames carry secret fields at
11//! full fidelity, and no cryptographic property is implied.
12
13use std::fmt;
14use std::sync::Arc;
15
16use crate::ids::FieldId;
17
18/// Renders the one stable redaction marker for a secret-classified field.
19///
20/// The exact shape — `[redacted:field_name]` — is a public contract: every
21/// display surface emits this form and the redaction sweep asserts its
22/// presence byte-for-byte. Change it nowhere or everywhere.
23#[must_use]
24pub fn secret_redaction_marker(field_name: &str) -> String {
25    format!("[redacted:{field_name}]")
26}
27
28/// One secret-classified field withheld from a released record.
29///
30/// Carries no value — only the field identity and the marker every display
31/// surface renders in the value's place. Constructing one is harmless by
32/// design (there is nothing inside to protect), which is why it may live
33/// here while the value-bearing wrapper is sealed inside `riffdb-policy`.
34#[derive(Clone, Eq, PartialEq)]
35pub struct RedactedSecretField {
36    field: FieldId,
37    field_name: Arc<str>,
38}
39
40impl RedactedSecretField {
41    /// Describes one withheld secret field.
42    #[must_use]
43    pub fn new(field: FieldId, field_name: impl Into<Arc<str>>) -> Self {
44        Self {
45            field,
46            field_name: field_name.into(),
47        }
48    }
49
50    /// The withheld field's stable ID.
51    #[must_use]
52    pub const fn field(&self) -> FieldId {
53        self.field
54    }
55
56    /// The withheld field's source name.
57    #[must_use]
58    pub fn field_name(&self) -> &str {
59        &self.field_name
60    }
61
62    /// The stable redaction marker for this field.
63    #[must_use]
64    pub fn redaction_marker(&self) -> String {
65        secret_redaction_marker(&self.field_name)
66    }
67}
68
69impl fmt::Debug for RedactedSecretField {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(formatter, "{}", secret_redaction_marker(&self.field_name))
72    }
73}
74
75impl fmt::Display for RedactedSecretField {
76    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(formatter, "{}", secret_redaction_marker(&self.field_name))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    /// The exact marker text is a public contract, tested once here and
86    /// consumed everywhere.
87    #[test]
88    fn marker_has_the_exact_documented_shape() {
89        assert_eq!(
90            secret_redaction_marker("token_hash"),
91            "[redacted:token_hash]"
92        );
93    }
94
95    /// The withheld-field descriptor renders only the marker in every form.
96    #[test]
97    fn descriptor_renders_only_the_marker() {
98        let redacted = RedactedSecretField::new(FieldId::first(), "token_hash");
99        assert_eq!(format!("{redacted}"), "[redacted:token_hash]");
100        assert_eq!(format!("{redacted:?}"), "[redacted:token_hash]");
101        assert_eq!(redacted.redaction_marker(), "[redacted:token_hash]");
102    }
103}