krabka_metadata/write_freeze.rs
1//! Topic write-freeze registry entries, replicated through the raft quorum
2//! with `MetadataRecord::V1TopicFreeze`.
3//!
4//! A freeze is a cluster state in which a topic stays readable and stays
5//! replicated, but the broker refuses every append that puts new
6//! externally-authored data into its log. Incident response, migrations, and
7//! disaster-recovery promotion all need that state, and an ACL edit cannot
8//! express it.
9//!
10//! The record stays pure data. The produce-path gate and the signature checks
11//! live in `krabka-broker`, and the resolution index lives on
12//! [`MetadataImage::topic_freeze`](crate::MetadataImage::topic_freeze).
13
14use serde::{Deserialize, Serialize};
15use uuid::Uuid;
16
17use crate::acl::PatternType;
18
19/// One entry in the write-freeze registry.
20///
21/// The entry is authoritative for its `(pattern_type, scope)` key: a later
22/// record with the same key replaces the earlier one. [`Self::frozen`] is the
23/// removal sentinel, in the same shape as `FeatureLevelRecord.level == 0`, so
24/// a thaw stays attributed in the raft log rather than vanishing as a
25/// tombstone.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct TopicFreezeRecord {
28 /// A literal topic name, or a topic-name prefix, as
29 /// [`Self::pattern_type`] selects.
30 pub scope: String,
31 /// The vocabulary that `kafka-acls` already gives an operator:
32 /// [`PatternType::Literal`] names one topic and
33 /// [`PatternType::Prefixed`] names a namespace.
34 pub pattern_type: PatternType,
35 /// `true` adds or replaces the entry. `false` removes it, and is the
36 /// thaw.
37 pub frozen: bool,
38 /// Free text that says why the freeze exists. It goes to the audit log
39 /// and to the producer's error message.
40 pub reason: String,
41 /// The principal that set the entry, in `KafkaPrincipal` string form,
42 /// for example `"User:alice"`.
43 pub set_by: String,
44 /// Epoch milliseconds at which the operator built the record. It is part
45 /// of the signed bytes, so an old signed thaw cannot be replayed.
46 pub set_at_ms: i64,
47 /// The break-glass approval that authorized a thaw. It is
48 /// [`Uuid::nil`] on a freeze, because a freeze needs no approval.
49 pub proposal_id: Uuid,
50 /// The operator key that signed the record. It is empty when the record
51 /// is unsigned.
52 pub key_id: String,
53 /// Detached Ed25519 signature over the canonical bytes of the record,
54 /// made on the machine of the operator. It is empty when the record is
55 /// unsigned. This crate keeps the bytes opaque; `krabka-broker` builds
56 /// and verifies them.
57 pub signature: Vec<u8>,
58}