1#[derive(Debug, Clone, serde::Serialize)]
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6pub struct GuardReport {
7 pub files: Vec<GuardFileReport>,
9}
10
11#[derive(Debug, Clone, serde::Serialize)]
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
14pub struct GuardFileReport {
15 pub path: String,
17 pub exists: bool,
19 pub zone: Option<GuardZone>,
21 pub boundary: GuardBoundary,
23 pub policy_rules: Vec<GuardPolicyRule>,
25 pub severities: GuardSeverities,
27 pub notes: Vec<String>,
29}
30
31#[derive(Debug, Clone, serde::Serialize)]
33#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
34pub struct GuardZone {
35 pub name: String,
37 pub patterns: Vec<String>,
39}
40
41#[derive(Debug, Clone, serde::Serialize)]
43#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44pub struct GuardBoundary {
45 pub configured: bool,
47 pub unrestricted: bool,
49 pub allowed_zones: Vec<String>,
51 pub allowed_type_only_zones: Vec<String>,
53 pub forbidden_calls: Vec<String>,
55 pub coverage_required: bool,
57}
58
59#[derive(Debug, Clone, serde::Serialize)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62pub struct GuardPolicyRule {
63 pub pack: String,
65 pub rule_id: String,
67 pub kind: String,
69 pub patterns: Vec<String>,
71 pub message: Option<String>,
73 pub severity: String,
75 pub suppress_token: String,
77}
78
79#[derive(Debug, Clone, serde::Serialize)]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82pub struct GuardSeverities {
83 pub boundary_violation: String,
85 pub policy_violation: String,
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn guard_file_report_serializes_expected_wire_shape() {
95 let report = GuardReport {
96 files: vec![GuardFileReport {
97 path: "src/domain/user.ts".to_string(),
98 exists: false,
99 zone: None,
100 boundary: GuardBoundary {
101 configured: true,
102 unrestricted: true,
103 allowed_zones: vec![],
104 allowed_type_only_zones: vec![],
105 forbidden_calls: vec!["child_process.*".to_string()],
106 coverage_required: true,
107 },
108 policy_rules: vec![GuardPolicyRule {
109 pack: "team-policy".to_string(),
110 rule_id: "pure-domain".to_string(),
111 kind: "banned-effect".to_string(),
112 patterns: vec!["network".to_string()],
113 message: Some("Inject effects via ports.".to_string()),
114 severity: "warn".to_string(),
115 suppress_token: "policy-violation:team-policy/pure-domain".to_string(),
116 }],
117 severities: GuardSeverities {
118 boundary_violation: "error".to_string(),
119 policy_violation: "warn".to_string(),
120 },
121 notes: vec!["Files outside every zone are unrestricted.".to_string()],
122 }],
123 };
124
125 let json = serde_json::to_value(report).unwrap();
126 let file = &json["files"][0];
127 assert_eq!(file["path"], "src/domain/user.ts");
128 assert_eq!(file["exists"], false);
129 assert!(file["zone"].is_null());
130 assert_eq!(file["boundary"]["allowed_zones"], serde_json::json!([]));
131 assert_eq!(
132 file["boundary"]["allowed_type_only_zones"],
133 serde_json::json!([])
134 );
135 assert_eq!(file["boundary"]["coverage_required"], true);
136 assert_eq!(file["policy_rules"][0]["rule_id"], "pure-domain");
137 assert_eq!(
138 file["policy_rules"][0]["suppress_token"],
139 "policy-violation:team-policy/pure-domain"
140 );
141 assert_eq!(file["severities"]["boundary_violation"], "error");
142 }
143}