Skip to main content

skill_veil_core/policy/
reports.rs

1use super::empty_finding_summary;
2use super::sarif::SarifReport;
3use super::types::{
4    ContextPolicy, PolicyAudit, PolicyFile, PolicyProfile, ShieldPolicy, SuppressionSummary,
5};
6use crate::analyzer::{
7    AgentExtensionKind, ArtifactClassification, ArtifactIdentitySource, StructuralValidity,
8};
9use crate::artifact_graph::ArtifactGraph;
10use crate::findings::{ArtifactKind, Finding, FindingSummary, PackageVerdictReport, Verdict};
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct JsonReport {
16    pub skill_name: String,
17    pub skill_path: String,
18    pub extension_kind: AgentExtensionKind,
19    pub classification: ArtifactClassification,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub package_id: Option<String>,
22    pub identity_source: ArtifactIdentitySource,
23    pub structural_validity: StructuralValidity,
24    pub heuristic_score: u8,
25    pub timestamp: DateTime<Utc>,
26    pub findings: Vec<Finding>,
27    #[serde(default)]
28    pub primary_findings: Vec<Finding>,
29    #[serde(default)]
30    pub supporting_findings: Vec<Finding>,
31    pub summary: FindingSummary,
32    #[serde(default = "empty_finding_summary")]
33    pub primary_summary: FindingSummary,
34    #[serde(default = "empty_finding_summary")]
35    pub supporting_summary: FindingSummary,
36    pub verdict: Verdict,
37    pub verdict_report: PackageVerdictReport,
38    pub artifact_graph: ArtifactGraph,
39    pub policies: Vec<ShieldPolicy>,
40    pub context_policies: Vec<ContextPolicy>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub profile: Option<PolicyProfile>,
43    #[serde(default)]
44    pub suppression_summary: SuppressionSummary,
45    #[serde(default)]
46    pub policy_audit: PolicyAudit,
47}
48
49pub struct PolicyGenerator {
50    skill_name: String,
51    skill_path: String,
52    primary_artifact_kind: ArtifactKind,
53    extension_kind: AgentExtensionKind,
54    classification: ArtifactClassification,
55    package_id: Option<String>,
56    identity_source: ArtifactIdentitySource,
57    structural_validity: StructuralValidity,
58    heuristic_score: u8,
59    findings: Vec<Finding>,
60    artifact_graph: ArtifactGraph,
61    profile: Option<PolicyProfile>,
62    policy: Option<PolicyFile>,
63    suppression_summary: SuppressionSummary,
64    policy_audit: PolicyAudit,
65    /// Pre-computed verdict report from the scan pipeline.
66    /// When present, serializers reuse this instead of re-deriving the verdict.
67    verdict_report: Option<PackageVerdictReport>,
68}
69
70impl PolicyGenerator {
71    /// The name of the skill being analyzed.
72    #[must_use]
73    pub fn skill_name(&self) -> &str {
74        &self.skill_name
75    }
76
77    /// Path to the primary skill artifact.
78    #[must_use]
79    pub fn skill_path(&self) -> &str {
80        &self.skill_path
81    }
82
83    /// The artifact kind of the primary entrypoint.
84    #[must_use]
85    pub fn primary_artifact_kind(&self) -> ArtifactKind {
86        self.primary_artifact_kind
87    }
88
89    /// The extension kind (Skill, AgentInstruction, etc.).
90    #[must_use]
91    pub fn extension_kind(&self) -> AgentExtensionKind {
92        self.extension_kind
93    }
94
95    /// The artifact classification result.
96    #[must_use]
97    pub fn classification(&self) -> ArtifactClassification {
98        self.classification
99    }
100
101    /// Optional package identifier.
102    #[must_use]
103    pub fn package_id(&self) -> Option<&str> {
104        self.package_id.as_deref()
105    }
106
107    /// How the artifact identity was determined.
108    #[must_use]
109    pub fn identity_source(&self) -> ArtifactIdentitySource {
110        self.identity_source
111    }
112
113    /// Structural validity of the analyzed artifact.
114    #[must_use]
115    pub fn structural_validity(&self) -> StructuralValidity {
116        self.structural_validity
117    }
118
119    /// Heuristic score assigned during analysis.
120    #[must_use]
121    pub fn heuristic_score(&self) -> u8 {
122        self.heuristic_score
123    }
124
125    /// The findings produced by analysis.
126    #[must_use]
127    pub fn findings(&self) -> &[Finding] {
128        &self.findings
129    }
130
131    /// The artifact dependency/capability graph.
132    #[must_use]
133    pub fn artifact_graph(&self) -> &ArtifactGraph {
134        &self.artifact_graph
135    }
136
137    /// The active policy profile, if any.
138    #[must_use]
139    pub fn profile(&self) -> Option<PolicyProfile> {
140        self.profile
141    }
142
143    /// The loaded policy file, if any.
144    #[must_use]
145    pub fn policy(&self) -> Option<&PolicyFile> {
146        self.policy.as_ref()
147    }
148
149    /// Summary of suppressed findings.
150    #[must_use]
151    pub fn suppression_summary(&self) -> &SuppressionSummary {
152        &self.suppression_summary
153    }
154
155    /// The policy audit trail.
156    #[must_use]
157    pub fn policy_audit(&self) -> &PolicyAudit {
158        &self.policy_audit
159    }
160
161    /// Pre-computed verdict report, if available.
162    #[must_use]
163    pub fn verdict_report(&self) -> Option<&PackageVerdictReport> {
164        self.verdict_report.as_ref()
165    }
166
167    pub fn new(
168        skill_name: impl Into<String>,
169        skill_path: impl Into<String>,
170        findings: Vec<Finding>,
171        artifact_graph: ArtifactGraph,
172    ) -> Self {
173        Self {
174            skill_name: skill_name.into(),
175            skill_path: skill_path.into(),
176            primary_artifact_kind: ArtifactKind::SkillDocument,
177            extension_kind: AgentExtensionKind::Skill,
178            classification: ArtifactClassification::ConfirmedSkill,
179            package_id: None,
180            identity_source: ArtifactIdentitySource::ExplicitName,
181            structural_validity: StructuralValidity::Confirmed,
182            heuristic_score: 0,
183            findings,
184            artifact_graph,
185            profile: None,
186            policy: None,
187            suppression_summary: SuppressionSummary::default(),
188            policy_audit: PolicyAudit::default(),
189            verdict_report: None,
190        }
191    }
192
193    #[must_use]
194    pub fn with_primary_artifact_kind(mut self, artifact_kind: ArtifactKind) -> Self {
195        self.primary_artifact_kind = artifact_kind;
196        self
197    }
198
199    #[must_use]
200    pub fn with_profile(mut self, profile: PolicyProfile) -> Self {
201        self.profile = Some(profile);
202        self
203    }
204
205    #[must_use]
206    pub fn with_extension_kind(mut self, extension_kind: AgentExtensionKind) -> Self {
207        self.extension_kind = extension_kind;
208        self
209    }
210
211    #[must_use]
212    pub fn with_classification(mut self, classification: ArtifactClassification) -> Self {
213        self.classification = classification;
214        self
215    }
216
217    #[must_use]
218    pub fn with_package_id(mut self, package_id: Option<String>) -> Self {
219        self.package_id = package_id;
220        self
221    }
222
223    #[must_use]
224    pub fn with_identity_source(mut self, identity_source: ArtifactIdentitySource) -> Self {
225        self.identity_source = identity_source;
226        self
227    }
228
229    #[must_use]
230    pub fn with_structural_validity(mut self, structural_validity: StructuralValidity) -> Self {
231        self.structural_validity = structural_validity;
232        self
233    }
234
235    #[must_use]
236    pub fn with_heuristic_score(mut self, heuristic_score: u8) -> Self {
237        self.heuristic_score = heuristic_score;
238        self
239    }
240
241    #[must_use]
242    pub fn with_policy(mut self, policy: PolicyFile) -> Self {
243        self.policy = Some(policy);
244        self
245    }
246
247    #[must_use]
248    pub fn with_suppression_summary(mut self, suppression_summary: SuppressionSummary) -> Self {
249        self.suppression_summary = suppression_summary;
250        self
251    }
252
253    #[must_use]
254    pub fn with_policy_audit(mut self, policy_audit: PolicyAudit) -> Self {
255        self.policy_audit = policy_audit;
256        self
257    }
258
259    #[must_use]
260    pub fn with_verdict_report(mut self, verdict_report: PackageVerdictReport) -> Self {
261        self.verdict_report = Some(verdict_report);
262        self
263    }
264
265    pub fn generate_shield_md(&self) -> String {
266        super::serializers::generate_shield_md(self)
267    }
268
269    pub fn generate_json(&self) -> JsonReport {
270        super::serializers::generate_json(self)
271    }
272
273    pub fn generate_sarif(&self) -> SarifReport {
274        super::serializers::generate_sarif(self)
275    }
276}