Skip to main content

prikk_store/
signature_diagnostics.rs

1//! Signature-envelope shape/order diagnostics: malformed algorithm shape, duplicate signature
2//! tuples, and non-canonical ordering, reported as separate `SignatureEnvelopeIssue`s alongside
3//! (not instead of) the hard rejection `ObjectEnvelope::validate_strict` already performs for the
4//! same three conditions. See `classify_signature_envelope`'s own doc for why, post-RFC-103, that
5//! makes this layer's non-empty-result path provably unreachable through `verify_repository`'s
6//! pipeline, and why the code stays regardless (DC-95 Stage 1 round 6's ruling on unreachable
7//! checks).
8
9use std::fmt;
10
11use prikk_error::Result;
12use prikk_object::{ObjectEnvelope, ObjectId, ObjectType};
13
14/// Persisted source of a signature-envelope diagnostic.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum SignatureEnvelopeSource {
17    /// Content-addressed object file.
18    Object {
19        /// Object type, ordered by its numeric registry code.
20        object_type: ObjectType,
21        /// Object identifier, ordered by its raw bytes.
22        object_id: ObjectId,
23    },
24    /// Active-session WAL record.
25    ActiveWal {
26        /// WAL sequence number.
27        sequence: u64,
28        /// Envelope object identifier.
29        object_id: ObjectId,
30    },
31    /// Inline ref-log record.
32    RefLog {
33        /// Canonical ref name.
34        ref_name: String,
35        /// One-based record sequence within the ref log.
36        sequence: u64,
37        /// RefUpdate object identifier.
38        object_id: ObjectId,
39    },
40}
41
42impl fmt::Display for SignatureEnvelopeSource {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::Object {
46                object_type,
47                object_id,
48            } => write!(formatter, "object {object_type} {object_id}"),
49            Self::ActiveWal {
50                sequence,
51                object_id,
52            } => write!(
53                formatter,
54                "active WAL sequence {sequence} object {object_id}"
55            ),
56            Self::RefLog {
57                ref_name,
58                sequence,
59                object_id,
60            } => write!(
61                formatter,
62                "ref log {ref_name} sequence {sequence} object {object_id}"
63            ),
64        }
65    }
66}
67
68/// One warning-level non-canonical signature-envelope condition.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct SignatureEnvelopeIssue {
71    /// Stable diagnostic code.
72    pub code: &'static str,
73    /// Persisted envelope source.
74    pub source: SignatureEnvelopeSource,
75    /// Human-readable diagnosis without host paths.
76    pub message: String,
77}
78
79/// RFC 103: every one of this function's three call sites (`verify.rs`, `verify/objects.rs`,
80/// `refs/verify.rs`) runs it immediately after a `crate::format::validate_read_schema(layout.format(),
81/// &envelope)?` on the same envelope, propagating on `Err` before this function is ever reached.
82/// `validate_read_schema` under `RepositoryFormat::CurrentV6` calls `envelope.validate_strict()`,
83/// which independently checks the exact same three conditions this function classifies
84/// (`signature_issues()`'s `malformed_shape`/`duplicate`/`noncanonical_order`) and hard-errors on any
85/// of them. With format-1 and format-2 both retired -- format-1's `validate_read_schema` branch
86/// checked only `schema_version`, never calling `validate_strict()` -- `CurrentV6` is the only format
87/// left, so any envelope this function would flag was already rejected one call earlier. **Provably
88/// unreachable through `verify_repository`'s pipeline**, the same shape as the rollback
89/// wrong-signature-length
90/// check (DC-95 Stage 1 round 11), but not a downgrade of a blocking check: Stage 1 already classified
91/// `signature_envelope_issues` from every source as "Excluded" -- it never backed a blocking
92/// predicate, for any source, even before this. Kept, untested, with the argument recorded (round 6's
93/// ruling on unreachable checks), since a caller could still construct an envelope directly and read
94/// its issues without going through `verify_repository` at all.
95pub(crate) fn classify_signature_envelope(
96    envelope: &ObjectEnvelope,
97    source: SignatureEnvelopeSource,
98) -> Result<Vec<SignatureEnvelopeIssue>> {
99    let conditions = envelope.signature_issues()?;
100    let mut issues = Vec::with_capacity(3);
101    if conditions.malformed_shape {
102        issues.push(issue(
103            "PRIKK-VERIFY-SIGNATURE-MALFORMED",
104            &source,
105            "envelope contains a signature with malformed algorithm shape",
106        ));
107    }
108    if conditions.duplicate {
109        issues.push(issue(
110            "PRIKK-VERIFY-SIGNATURE-DUPLICATE",
111            &source,
112            "envelope contains a duplicate signature tuple",
113        ));
114    }
115    if conditions.noncanonical_order {
116        issues.push(issue(
117            "PRIKK-VERIFY-SIGNATURE-NONCANONICAL-ORDER",
118            &source,
119            "envelope signatures are not in canonical order",
120        ));
121    }
122    Ok(issues)
123}
124
125fn issue(
126    code: &'static str,
127    source: &SignatureEnvelopeSource,
128    message: &str,
129) -> SignatureEnvelopeIssue {
130    SignatureEnvelopeIssue {
131        code,
132        source: source.clone(),
133        message: message.to_string(),
134    }
135}