Skip to main content

secunit_core/evidence/
manifest.rs

1//! Strongly-typed prepare / result / manifest payloads. Each maps 1:1
2//! to its JSON Schema under `schemas/`.
3
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use crate::model::ResolvedSystem;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "kebab-case")]
14pub enum ScopeLayout {
15    BySystem,
16    Flat,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum RunOutcome {
22    Complete,
23    Partial,
24    Failed,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "lowercase")]
29pub enum SystemOutcome {
30    Complete,
31    Skipped,
32    Failed,
33}
34
35// ---------- prepare context -------------------------------------------------
36
37/// The skill `run prepare` resolved for this control, so the agent can
38/// load it without knowing whether it ships in the binary or lives under
39/// `skills/`. Resolution is local-first, then bundled.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SkillRef {
42    pub name: String,
43    /// `"local"` (a file under `skills/`) or `"bundled"` (shipped in the
44    /// binary; load it with `secunit skills show <name>`).
45    pub source: String,
46    pub sha256: String,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PrepareContext {
51    pub schema_version: u32,
52    pub control_id: String,
53    pub run_id: String,
54    pub run_dir: PathBuf,
55    pub started_at: DateTime<Utc>,
56    /// The resolved runbook to follow for this run.
57    pub skill: SkillRef,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub operator: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub note: Option<String>,
62    pub scope_layout: ScopeLayout,
63    pub resolved_scope: Vec<ResolvedSystem>,
64    pub registry_git_sha: String,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub period_id: Option<String>,
67}
68
69// ---------- result (skill output) ------------------------------------------
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct RunResult {
73    pub schema_version: u32,
74    pub control_id: String,
75    pub run_id: String,
76    pub status: RunOutcome,
77    #[serde(default)]
78    pub by_system: Vec<SystemResult>,
79    #[serde(default)]
80    pub draft_risks: Vec<serde_json::Value>,
81    #[serde(default)]
82    pub draft_issues: Vec<serde_json::Value>,
83    #[serde(default)]
84    pub external_links: Vec<serde_json::Value>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct SystemResult {
89    pub name: String,
90    pub status: SystemOutcome,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub note: Option<String>,
93}
94
95// ---------- finalized manifest ---------------------------------------------
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct AgentInfo {
99    pub model: String,
100    pub skill: String,
101    pub skill_sha256: String,
102    pub control_sha256: String,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct PriorRun {
107    pub run_id: String,
108    pub manifest_sha256: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct Artifact {
113    pub path: String,
114    pub sha256: String,
115    pub bytes: u64,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct BySystemBlock {
120    pub name: String,
121    pub status: SystemOutcome,
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub summary: Option<BTreeMap<String, serde_json::Value>>,
124    pub artifacts: Vec<Artifact>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Manifest {
129    pub schema_version: u32,
130    pub control_id: String,
131    pub run_id: String,
132    pub started_at: DateTime<Utc>,
133    pub completed_at: DateTime<Utc>,
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub operator: Option<String>,
136    pub agent: AgentInfo,
137    pub registry_git_sha: String,
138    pub scope_layout: ScopeLayout,
139    pub resolved_scope: Vec<ResolvedSystem>,
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub prior_run: Option<PriorRun>,
142    pub artifacts: Vec<Artifact>,
143    #[serde(default, skip_serializing_if = "Vec::is_empty")]
144    pub by_system: Vec<BySystemBlock>,
145    pub status: RunOutcome,
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub failure_reason: Option<String>,
148    #[serde(default)]
149    pub draft_risks: Vec<serde_json::Value>,
150    #[serde(default)]
151    pub draft_issues: Vec<serde_json::Value>,
152    #[serde(default)]
153    pub external_links: Vec<serde_json::Value>,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub period_id: Option<String>,
156}