wist_api/action_result/
v1.rs1use serde::{Deserialize, Serialize};
7
8use wist_contracts::API_VERSION_V1;
9
10use super::{ActionResult, FinalStatus};
11
12pub const API_VERSION: &str = API_VERSION_V1;
14
15pub const REPORT_ACTION_RESULT_KIND: &str = "report_action_result";
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ReportActionResult {
20 pub api_version: String,
21 pub report_id: String,
22 pub kind: String,
23 pub dispatch_id: Option<String>,
24 pub action_id: String,
25 pub report_attempt: u32,
26 pub final_status: FinalStatus,
27 pub execution_id: String,
28 pub plan_digest: String,
29 pub agent_id: String,
30 pub instance_id: String,
31 pub result_attestation: ResultAttestation,
32 pub reported_at: String,
33 pub result: ActionResult,
34}
35
36impl ReportActionResult {
37 #[allow(clippy::too_many_arguments)]
38 pub fn new(
39 report_id: String,
40 action_id: String,
41 report_attempt: u32,
42 final_status: FinalStatus,
43 execution_id: String,
44 plan_digest: String,
45 agent_id: String,
46 instance_id: String,
47 result_attestation: ResultAttestation,
48 reported_at: String,
49 result: ActionResult,
50 ) -> Self {
51 Self {
52 api_version: API_VERSION_V1.to_string(),
53 report_id,
54 kind: REPORT_ACTION_RESULT_KIND.to_string(),
55 dispatch_id: None,
56 action_id,
57 report_attempt,
58 final_status,
59 execution_id,
60 plan_digest,
61 agent_id,
62 instance_id,
63 result_attestation,
64 reported_at,
65 result,
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(deny_unknown_fields)]
72pub struct ResultAttestation {
73 pub result_digest: String,
75 pub signature: String,
77 pub issued_by: String,
79 pub attested_at: String,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct ActionResultAck {
86 pub report_id: String,
87 pub agent_id: String,
88 pub acknowledged_at: String,
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn attestation() -> ResultAttestation {
96 ResultAttestation {
97 result_digest: "sha256:abc".to_string(),
98 signature: "dev-placeholder:sig".to_string(),
99 issued_by: "dev-placeholder:agent-1".to_string(),
100 attested_at: "2026-09-27T00:00:02Z".to_string(),
101 }
102 }
103
104 #[test]
105 fn report_action_result_new_sets_kind_and_leaves_dispatch_absent() {
106 let result = ActionResult::new(
107 "act-1".to_string(),
108 "exec-1".to_string(),
109 FinalStatus::Succeeded,
110 );
111 let report = ReportActionResult::new(
112 "rep-1".to_string(),
113 "act-1".to_string(),
114 1,
115 FinalStatus::Succeeded,
116 "exec-1".to_string(),
117 "sha256:plan".to_string(),
118 "agent-1".to_string(),
119 "inst-1".to_string(),
120 attestation(),
121 "2026-09-27T00:00:02Z".to_string(),
122 result,
123 );
124 assert_eq!(report.api_version, API_VERSION_V1);
125 assert_eq!(report.kind, REPORT_ACTION_RESULT_KIND);
126 assert_eq!(report.dispatch_id, None);
127
128 let json = serde_json::to_string(&report).expect("encode");
129 let back: ReportActionResult = serde_json::from_str(&json).expect("decode");
130 assert_eq!(back, report);
131 }
132}