1#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8pub const SEG_FEDERATED_MANIFEST: u8 = 0x33;
12pub const SEG_DIFF_PRIVACY_PROOF: u8 = 0x34;
14pub const SEG_REDACTION_LOG: u8 = 0x35;
16pub const SEG_AGGREGATE_WEIGHTS: u8 = 0x36;
18
19#[derive(Clone, Debug, PartialEq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub struct FederatedManifest {
27 pub format_version: u32,
29 pub contributor_pseudonym: String,
31 pub export_timestamp_s: u64,
33 pub included_segment_ids: Vec<u64>,
35 pub privacy_budget_spent: f64,
37 pub domain_id: String,
39 pub rvf_version_tag: String,
41 pub trajectory_count: u64,
43 pub avg_quality_score: f64,
45}
46
47impl FederatedManifest {
48 pub fn new(contributor_pseudonym: String, domain_id: String) -> Self {
50 Self {
51 format_version: 1,
52 contributor_pseudonym,
53 export_timestamp_s: 0,
54 included_segment_ids: Vec::new(),
55 privacy_budget_spent: 0.0,
56 domain_id,
57 rvf_version_tag: String::from("rvf-v1"),
58 trajectory_count: 0,
59 avg_quality_score: 0.0,
60 }
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub enum NoiseMechanism {
70 Gaussian,
72 Laplace,
74}
75
76#[derive(Clone, Debug, PartialEq)]
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
81pub struct DiffPrivacyProof {
82 pub epsilon: f64,
84 pub delta: f64,
86 pub mechanism: NoiseMechanism,
88 pub sensitivity: f64,
90 pub clipping_norm: f64,
92 pub noise_scale: f64,
94 pub noised_parameter_count: u64,
96}
97
98impl DiffPrivacyProof {
99 pub fn gaussian(epsilon: f64, delta: f64, sensitivity: f64, clipping_norm: f64) -> Self {
101 let sigma = sensitivity * (2.0_f64 * (1.25_f64 / delta).ln()).sqrt() / epsilon;
102 Self {
103 epsilon,
104 delta,
105 mechanism: NoiseMechanism::Gaussian,
106 sensitivity,
107 clipping_norm,
108 noise_scale: sigma,
109 noised_parameter_count: 0,
110 }
111 }
112
113 pub fn laplace(epsilon: f64, sensitivity: f64, clipping_norm: f64) -> Self {
115 let b = sensitivity / epsilon;
116 Self {
117 epsilon,
118 delta: 0.0,
119 mechanism: NoiseMechanism::Laplace,
120 sensitivity,
121 clipping_norm,
122 noise_scale: b,
123 noised_parameter_count: 0,
124 }
125 }
126}
127
128#[derive(Clone, Debug, PartialEq)]
132#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
133pub struct RedactionEntry {
134 pub category: String,
136 pub count: u32,
138 pub rule_id: String,
140}
141
142#[derive(Clone, Debug, PartialEq)]
146#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
147pub struct RedactionLog {
148 pub entries: Vec<RedactionEntry>,
150 pub pre_redaction_hash: [u8; 32],
152 pub fields_scanned: u64,
154 pub total_redactions: u64,
156 pub timestamp_s: u64,
158}
159
160impl RedactionLog {
161 pub fn new() -> Self {
163 Self {
164 entries: Vec::new(),
165 pre_redaction_hash: [0u8; 32],
166 fields_scanned: 0,
167 total_redactions: 0,
168 timestamp_s: 0,
169 }
170 }
171
172 pub fn add_entry(&mut self, category: &str, count: u32, rule_id: &str) {
174 self.total_redactions += count as u64;
175 self.entries.push(RedactionEntry {
176 category: category.to_string(),
177 count,
178 rule_id: rule_id.to_string(),
179 });
180 }
181}
182
183impl Default for RedactionLog {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[derive(Clone, Debug, PartialEq)]
193#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
194pub struct AggregateWeights {
195 pub round: u64,
197 pub participation_count: u32,
199 pub lora_deltas: Vec<f64>,
201 pub confidences: Vec<f64>,
203 pub mean_loss: f64,
205 pub loss_variance: f64,
207 pub domain_id: String,
209 pub byzantine_filtered: bool,
211 pub outliers_removed: u32,
213}
214
215impl AggregateWeights {
216 pub fn new(domain_id: String, round: u64) -> Self {
218 Self {
219 round,
220 participation_count: 0,
221 lora_deltas: Vec::new(),
222 confidences: Vec::new(),
223 mean_loss: 0.0,
224 loss_variance: 0.0,
225 domain_id,
226 byzantine_filtered: false,
227 outliers_removed: 0,
228 }
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq)]
238#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
239pub struct BetaParams {
240 pub alpha: f64,
242 pub beta: f64,
244}
245
246impl BetaParams {
247 pub fn new(alpha: f64, beta: f64) -> Self {
249 Self { alpha, beta }
250 }
251
252 pub fn uniform() -> Self {
254 Self { alpha: 1.0, beta: 1.0 }
255 }
256
257 pub fn mean(&self) -> f64 {
259 self.alpha / (self.alpha + self.beta)
260 }
261
262 pub fn observations(&self) -> f64 {
264 self.alpha + self.beta - 2.0
265 }
266
267 pub fn merge(&self, other: &BetaParams) -> BetaParams {
269 BetaParams {
270 alpha: self.alpha + other.alpha - 1.0,
271 beta: self.beta + other.beta - 1.0,
272 }
273 }
274
275 pub fn dampen(&self, factor: f64) -> BetaParams {
277 let f = factor.clamp(0.0, 1.0);
278 BetaParams {
279 alpha: 1.0 + (self.alpha - 1.0) * f,
280 beta: 1.0 + (self.beta - 1.0) * f,
281 }
282 }
283}
284
285impl Default for BetaParams {
286 fn default() -> Self {
287 Self::uniform()
288 }
289}
290
291#[derive(Clone, Debug, PartialEq)]
295#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
296pub struct TransferPriorEntry {
297 pub bucket_id: String,
299 pub arm_id: String,
301 pub params: BetaParams,
303 pub observation_count: u64,
305}
306
307#[derive(Clone, Debug, PartialEq)]
309#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
310pub struct TransferPriorSet {
311 pub source_domain: String,
313 pub entries: Vec<TransferPriorEntry>,
315 pub cost_ema: f64,
317}
318
319#[derive(Clone, Debug, PartialEq)]
323#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
324pub struct PolicyKernelSnapshot {
325 pub kernel_id: String,
327 pub knobs: Vec<f64>,
329 pub fitness: f64,
331 pub generation: u64,
333}
334
335#[derive(Clone, Debug, PartialEq)]
339#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
340pub struct CostCurveSnapshot {
341 pub domain_id: String,
343 pub points: Vec<(u64, f64)>,
345 pub acceleration: f64,
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn segment_type_constants() {
355 assert_eq!(SEG_FEDERATED_MANIFEST, 0x33);
356 assert_eq!(SEG_DIFF_PRIVACY_PROOF, 0x34);
357 assert_eq!(SEG_REDACTION_LOG, 0x35);
358 assert_eq!(SEG_AGGREGATE_WEIGHTS, 0x36);
359 }
360
361 #[test]
362 fn federated_manifest_new() {
363 let m = FederatedManifest::new("alice".into(), "genomics".into());
364 assert_eq!(m.format_version, 1);
365 assert_eq!(m.contributor_pseudonym, "alice");
366 assert_eq!(m.domain_id, "genomics");
367 assert_eq!(m.trajectory_count, 0);
368 }
369
370 #[test]
371 fn diff_privacy_proof_gaussian() {
372 let p = DiffPrivacyProof::gaussian(1.0, 1e-5, 1.0, 1.0);
373 assert_eq!(p.mechanism, NoiseMechanism::Gaussian);
374 assert!(p.noise_scale > 0.0);
375 assert_eq!(p.epsilon, 1.0);
376 }
377
378 #[test]
379 fn diff_privacy_proof_laplace() {
380 let p = DiffPrivacyProof::laplace(1.0, 1.0, 1.0);
381 assert_eq!(p.mechanism, NoiseMechanism::Laplace);
382 assert!((p.noise_scale - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn redaction_log_add_entry() {
387 let mut log = RedactionLog::new();
388 log.add_entry("path", 3, "rule_path_unix");
389 log.add_entry("ip", 2, "rule_ipv4");
390 assert_eq!(log.entries.len(), 2);
391 assert_eq!(log.total_redactions, 5);
392 }
393
394 #[test]
395 fn aggregate_weights_new() {
396 let w = AggregateWeights::new("code_review".into(), 1);
397 assert_eq!(w.round, 1);
398 assert_eq!(w.participation_count, 0);
399 assert!(!w.byzantine_filtered);
400 }
401
402 #[test]
403 fn beta_params_merge() {
404 let a = BetaParams::new(10.0, 5.0);
405 let b = BetaParams::new(8.0, 3.0);
406 let merged = a.merge(&b);
407 assert!((merged.alpha - 17.0).abs() < 1e-10);
408 assert!((merged.beta - 7.0).abs() < 1e-10);
409 }
410
411 #[test]
412 fn beta_params_dampen() {
413 let p = BetaParams::new(10.0, 5.0);
414 let dampened = p.dampen(0.25);
415 assert!((dampened.alpha - 3.25).abs() < 1e-10);
417 assert!((dampened.beta - 2.0).abs() < 1e-10);
419 }
420
421 #[test]
422 fn beta_params_mean() {
423 let p = BetaParams::new(10.0, 10.0);
424 assert!((p.mean() - 0.5).abs() < 1e-10);
425 }
426}