1use std::collections::BTreeMap;
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15pub const SENSOR_REPORT_V1_SCHEMA_JSON: &str = include_str!("../schemas/sensor.report.v1.json");
17
18pub const COCKPIT_REPORT_V1_SCHEMA_JSON: &str = include_str!("../schemas/cockpit.report.v1.json");
20
21pub const BUILDFIX_PLAN_V1_SCHEMA_JSON: &str = include_str!("../schemas/buildfix.plan.v1.json");
23
24pub const COCKPIT_PROMOTE_V1_SCHEMA_JSON: &str = include_str!("../schemas/cockpit.promote.v1.json");
26
27pub type SchemaId = String;
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "snake_case")]
32pub enum VerdictStatus {
33 Pass,
34 Warn,
35 Fail,
36 Skip,
37}
38
39fn is_zero(v: &u64) -> bool {
40 *v == 0
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
44pub struct VerdictCounts {
45 pub info: u64,
46 pub warn: u64,
47 pub error: u64,
48 #[serde(default, skip_serializing_if = "is_zero")]
49 pub suppressed: u64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct Verdict {
54 pub status: VerdictStatus,
55 pub counts: VerdictCounts,
56 #[serde(default)]
57 pub reasons: Vec<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61pub struct ToolInfo {
62 pub name: String,
63 pub version: String,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub commit: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69pub struct HostInfo {
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub os: Option<String>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub arch: Option<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub hostname: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79pub struct GitInfo {
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub repo: Option<String>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub base_ref: Option<String>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub head_ref: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub base_sha: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub head_sha: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub merge_base: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
95pub struct CiInfo {
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub provider: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub run_id: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub run_url: Option<String>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub job: Option<String>,
104}
105
106#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "snake_case")]
108pub enum CapabilityStatus {
109 Available,
110 Unavailable,
111 Skipped,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
115pub struct Capability {
116 pub status: CapabilityStatus,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub reason: Option<String>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122pub struct RunInfo {
123 pub started_at: String, #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub ended_at: Option<String>,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub duration_ms: Option<u64>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub host: Option<HostInfo>,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub git: Option<GitInfo>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub ci: Option<CiInfo>,
134 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
136 pub capabilities: BTreeMap<String, Capability>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
140#[serde(rename_all = "snake_case")]
141pub enum Severity {
142 Info,
143 Warn,
144 Error,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
148pub struct Location {
149 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub path: Option<String>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub line: Option<u32>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub col: Option<u32>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub struct Finding {
159 pub severity: Severity,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub check_id: Option<String>,
162 pub code: String,
163 pub message: String,
164
165 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub location: Option<Location>,
167
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub help: Option<String>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub url: Option<String>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub fingerprint: Option<String>,
174
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub data: Option<Value>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
181pub struct SensorReport {
182 pub schema: SchemaId,
183 pub tool: ToolInfo,
184 pub run: RunInfo,
185 pub verdict: Verdict,
186 #[serde(default)]
187 pub findings: Vec<Finding>,
188 #[serde(default, skip_serializing_if = "Vec::is_empty")]
189 pub artifacts: Vec<ArtifactPointer>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub data: Option<Value>,
192}
193
194#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
196#[serde(rename_all = "snake_case")]
197pub enum MissingPolicy {
198 #[default]
199 Skip,
200 Warn,
201 Fail,
202}
203
204#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
206#[serde(rename_all = "snake_case")]
207pub enum Presence {
208 Present,
209 Missing,
210 Invalid,
211}
212
213#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
215#[serde(rename_all = "snake_case")]
216pub enum PolicyOutcome {
217 Blocked,
218 Allowed,
219 Informational,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
224pub struct ArtifactPointer {
225 pub id: String,
226 pub path: String,
227 pub mime: String,
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub schema: Option<String>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
234pub struct CockpitPromoteHints {
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub schema: Option<String>,
237 #[serde(default, skip_serializing_if = "Vec::is_empty")]
238 pub cards: Vec<PromoteCard>,
239 #[serde(default, skip_serializing_if = "Vec::is_empty")]
240 pub suggested_highlights: Vec<SuggestedHighlight>,
241 #[serde(default, skip_serializing_if = "Vec::is_empty")]
242 pub suggested_artifacts: Vec<SuggestedArtifact>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
247pub struct PromoteCard {
248 pub id: String,
249 pub label: String,
250 pub value: String,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub severity: Option<Severity>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
257pub struct SuggestedHighlight {
258 pub finding_fingerprint: String,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
263pub struct SuggestedArtifact {
264 pub artifact_id: String,
265}
266
267#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
269#[serde(rename_all = "snake_case")]
270pub enum SchemaValidation {
271 #[default]
273 Lax,
274 Strict,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
280pub struct SensorPolicy {
281 #[serde(default)]
282 pub blocking: bool,
283 #[serde(default)]
284 pub missing: MissingPolicy,
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub section: Option<String>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
288 pub require_label: Option<String>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub repro: Option<String>,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
295pub struct BuildfixPolicy {
296 #[serde(default)]
298 pub auto_apply: bool,
299 #[serde(default = "default_buildfix_max_auto_apply_safety")]
301 pub max_auto_apply_safety: SafetyLevel,
302 #[serde(default = "default_buildfix_require_matched_finding")]
304 pub require_matched_finding: bool,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub actuator: Option<BuildfixActuatorConfig>,
308}
309
310impl Default for BuildfixPolicy {
311 fn default() -> Self {
312 Self {
313 auto_apply: false,
314 max_auto_apply_safety: default_buildfix_max_auto_apply_safety(),
315 require_matched_finding: default_buildfix_require_matched_finding(),
316 actuator: None,
317 }
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
323pub struct BuildfixActuatorConfig {
324 pub command: String,
325 #[serde(default = "default_buildfix_actuator_timeout_ms")]
326 pub timeout_ms: u64,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
330pub struct Policy {
331 #[serde(default)]
332 pub warn_is_fail: bool,
333 #[serde(default = "default_max_highlights")]
334 pub max_highlights: usize,
335 #[serde(default = "default_max_per_sensor_findings")]
336 pub max_per_sensor_findings: usize,
337 #[serde(default = "default_max_annotations")]
338 pub max_annotations: usize,
339 #[serde(default = "default_section_order")]
340 pub section_order: Vec<String>,
341 #[serde(default)]
344 pub schema_validation: SchemaValidation,
345 #[serde(default = "default_max_receipt_size_bytes")]
348 pub max_receipt_size_bytes: usize,
349}
350
351fn default_max_highlights() -> usize {
352 7
353}
354fn default_max_per_sensor_findings() -> usize {
355 20
356}
357fn default_max_annotations() -> usize {
358 25
359}
360fn default_max_receipt_size_bytes() -> usize {
361 2 * 1024 * 1024 }
363fn default_buildfix_max_auto_apply_safety() -> SafetyLevel {
364 SafetyLevel::Safe
365}
366fn default_buildfix_require_matched_finding() -> bool {
367 true
368}
369fn default_buildfix_actuator_timeout_ms() -> u64 {
370 30_000 }
372fn default_section_order() -> Vec<String> {
373 vec![
374 "Highlights".into(),
375 "Repo contract".into(),
376 "Dependencies".into(),
377 "Policy".into(),
378 "Tests".into(),
379 "Diagnostics".into(),
380 "Performance".into(),
381 "Environment".into(),
382 "Other".into(),
383 ]
384}
385
386impl Default for Policy {
387 fn default() -> Self {
388 Self {
389 warn_is_fail: false,
390 max_highlights: default_max_highlights(),
391 max_per_sensor_findings: default_max_per_sensor_findings(),
392 max_annotations: default_max_annotations(),
393 section_order: default_section_order(),
394 schema_validation: SchemaValidation::default(),
395 max_receipt_size_bytes: default_max_receipt_size_bytes(),
396 }
397 }
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
401pub struct CockpitConfig {
402 #[serde(default)]
403 pub policy: Policy,
404 #[serde(default)]
405 pub buildfix: BuildfixPolicy,
406 #[serde(default)]
407 pub policy_signing: PolicySigningConfig,
408 #[serde(default)]
409 pub sensors: std::collections::BTreeMap<String, SensorPolicy>,
410 #[serde(default, skip_serializing_if = "Vec::is_empty")]
411 pub hooks: Vec<HookConfig>,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
416pub struct SensorSummary {
417 pub id: String,
418 pub blocking: bool,
419 pub missing: MissingPolicy,
420 pub presence: Presence,
421 pub report_path: String,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub comment_path: Option<String>,
424 pub verdict: Verdict,
425 #[serde(default)]
426 pub truncated: bool,
427 #[serde(default)]
428 pub errors: Vec<String>,
429 #[serde(default, skip_serializing_if = "Option::is_none")]
430 pub missing_policy_applied: Option<MissingPolicy>,
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub policy_outcome: Option<PolicyOutcome>,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
436pub struct Highlight {
437 pub sensor_id: String,
438 pub finding: Finding,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
443pub struct CockpitReport {
444 pub schema: SchemaId,
445 pub tool: ToolInfo,
446 pub run: RunInfo,
447 pub verdict: Verdict,
448 pub sensors: Vec<SensorSummary>,
449 pub highlights: Vec<Highlight>,
450 pub policy: PolicySnapshot,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub data: Option<Value>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
456pub struct PolicySnapshot {
457 pub warn_is_fail: bool,
458 pub max_highlights: usize,
459 pub max_per_sensor_findings: usize,
460 pub max_annotations: usize,
461 pub section_order: Vec<String>,
462 pub sensors: Vec<PolicySensorSnapshot>,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
466pub struct PolicySensorSnapshot {
467 pub id: String,
468 pub blocking: bool,
469 pub missing: MissingPolicy,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub section: Option<String>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub require_label: Option<String>,
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub repro: Option<String>,
476}
477
478#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
480pub struct FindingSortKey {
481 pub severity_rank: u8,
482 pub sensor_id: String,
483 pub path: String,
484 pub line: u32,
485 pub code: String,
486 pub message: String,
487}
488
489pub fn severity_rank(s: &Severity) -> u8 {
490 match s {
491 Severity::Error => 0,
492 Severity::Warn => 1,
493 Severity::Info => 2,
494 }
495}
496
497pub fn verdict_status_rank(s: &VerdictStatus) -> u8 {
498 match s {
499 VerdictStatus::Fail => 0,
500 VerdictStatus::Warn => 1,
501 VerdictStatus::Pass => 2,
502 VerdictStatus::Skip => 3,
503 }
504}
505
506pub fn is_valid_sensor_id(id: &str) -> bool {
508 !id.is_empty()
509 && !id.contains("..")
510 && !id.contains('/')
511 && !id.contains('\\')
512 && id
513 .bytes()
514 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
515}
516
517#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
523#[serde(rename_all = "snake_case")]
524pub enum SafetyLevel {
525 Safe,
527 Guarded,
529 Unsafe,
531}
532
533pub fn safety_level_rank(s: &SafetyLevel) -> u8 {
536 match s {
537 SafetyLevel::Safe => 0,
538 SafetyLevel::Guarded => 1,
539 SafetyLevel::Unsafe => 2,
540 }
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
545pub struct FindingRef {
546 pub sensor_id: String,
547 #[serde(default, skip_serializing_if = "Option::is_none")]
548 pub fingerprint: Option<String>,
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub code: Option<String>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub tool: Option<String>,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 pub check_id: Option<String>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
559pub struct Preconditions {
560 pub repo_head: String,
561 #[serde(default, skip_serializing_if = "Vec::is_empty")]
562 pub receipt_digests: Vec<String>,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
567pub struct Fix {
568 pub id: String,
569 pub safety: SafetyLevel,
570 pub description: String,
571 #[serde(default, skip_serializing_if = "Vec::is_empty")]
572 pub finding_refs: Vec<FindingRef>,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
574 pub preconditions: Option<Preconditions>,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub data: Option<Value>,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
581pub struct BuildfixPlan {
582 pub schema: SchemaId,
583 pub tool: ToolInfo,
584 pub fixes: Vec<Fix>,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
593pub struct VerdictChange {
594 pub before: VerdictStatus,
595 pub after: VerdictStatus,
596}
597
598#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
600pub struct CountDeltas {
601 pub info_delta: i64,
602 pub warn_delta: i64,
603 pub error_delta: i64,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
608pub struct TrendFinding {
609 pub sensor_id: String,
610 pub code: String,
611 pub message: String,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub path: Option<String>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub line: Option<u32>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub fingerprint: Option<String>,
618 pub severity: Severity,
619}
620
621#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
623#[serde(rename_all = "snake_case")]
624pub enum TrendChange {
625 New,
626 Fixed,
627}
628
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
631pub struct TrendDelta {
632 pub verdict_change: Option<VerdictChange>,
633 pub count_deltas: CountDeltas,
634 pub new_findings: Vec<TrendFinding>,
635 pub fixed_findings: Vec<TrendFinding>,
636 pub sensors_added: Vec<String>,
637 pub sensors_removed: Vec<String>,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
646pub struct MatchedFinding {
647 pub sensor_id: String,
648 pub code: String,
649 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub fingerprint: Option<String>,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
655pub struct FixSummary {
656 pub fix_id: String,
657 pub sensor_id: String,
658 pub safety: SafetyLevel,
659 pub description: String,
660 pub matched_findings: Vec<MatchedFinding>,
661 pub unmatched: bool,
662}
663
664#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
666pub struct BuildfixSummary {
667 pub fixes: Vec<FixSummary>,
668 pub total_fixes: usize,
669 pub matched_count: usize,
670 pub unmatched_count: usize,
671}
672
673pub const BUILDFIX_APPLY_REQUEST_SCHEMA_ID: &str = "buildfix.apply.request.v1";
675
676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
678pub struct BuildfixApplyRequest {
679 pub schema: SchemaId,
680 pub max_auto_apply_safety: SafetyLevel,
681 pub require_matched_finding: bool,
682 pub fixes: Vec<FixSummary>,
683}
684
685#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
687pub struct BuildfixActuatorResult {
688 #[serde(default)]
689 pub applied_fix_ids: Vec<String>,
690 #[serde(default)]
691 pub skipped_fix_ids: Vec<String>,
692 #[serde(default)]
693 pub errors: Vec<String>,
694}
695
696#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
698#[serde(rename_all = "snake_case")]
699pub enum BuildfixApplyStatus {
700 Skipped,
701 Applied,
702 Failed,
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
707pub struct BuildfixApplySummary {
708 pub status: BuildfixApplyStatus,
709 pub auto_apply_enabled: bool,
710 pub max_auto_apply_safety: SafetyLevel,
711 pub require_matched_finding: bool,
712 #[serde(default, skip_serializing_if = "Vec::is_empty")]
713 pub candidate_fix_ids: Vec<String>,
714 #[serde(default, skip_serializing_if = "Vec::is_empty")]
715 pub selected_fix_ids: Vec<String>,
716 #[serde(default, skip_serializing_if = "Vec::is_empty")]
717 pub applied_fix_ids: Vec<String>,
718 #[serde(default, skip_serializing_if = "Vec::is_empty")]
719 pub skipped_fix_ids: Vec<String>,
720 #[serde(default, skip_serializing_if = "Vec::is_empty")]
721 pub errors: Vec<String>,
722 #[serde(default, skip_serializing_if = "Option::is_none")]
723 pub reason: Option<String>,
724 #[serde(default, skip_serializing_if = "Option::is_none")]
725 pub actuator_command: Option<String>,
726}
727
728#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
734#[serde(rename_all = "snake_case")]
735pub enum PolicySignatureAlgorithm {
736 #[default]
738 HmacSha256,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
743pub struct PolicySigningConfig {
744 #[serde(default)]
746 pub enabled: bool,
747 #[serde(default)]
749 pub algorithm: PolicySignatureAlgorithm,
750 #[serde(default, skip_serializing_if = "Option::is_none")]
752 pub key_path: Option<String>,
753 #[serde(default, skip_serializing_if = "Option::is_none")]
755 pub key_env: Option<String>,
756 #[serde(default, skip_serializing_if = "Option::is_none")]
758 pub key_id: Option<String>,
759}
760
761pub const POLICY_SIGNATURE_SCHEMA_ID: &str = "cockpit.policy_signature.v1";
763
764#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
766pub struct PolicySignatureEvidence {
767 pub schema: SchemaId,
768 pub algorithm: PolicySignatureAlgorithm,
769 pub policy_sha256: String,
771 pub signature: String,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
774 pub key_id: Option<String>,
775}
776
777#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
783#[serde(rename_all = "snake_case")]
784pub enum HookWhen {
785 #[default]
787 AfterIngest,
788}
789
790#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
792pub struct HookConfig {
793 pub name: String,
794 pub command: String,
795 #[serde(default)]
796 pub when: HookWhen,
797 #[serde(default = "default_hook_timeout_ms")]
798 pub timeout_ms: u64,
799}
800
801fn default_hook_timeout_ms() -> u64 {
802 default_buildfix_actuator_timeout_ms()
803}