Skip to main content

wist_api/action_result/
v1.rs

1//! `agent/action-results` seam —— **v1** 基线。
2//!
3//! 冻结基线:只做**加性**兼容不动它;非加性变更就新开 `v2`。
4//! 约定见 `wist-design/doc/design/foundation/api-seam-inventory.md` §7。
5
6use serde::{Deserialize, Serialize};
7
8use wist_contracts::API_VERSION_V1;
9
10use super::{ActionResult, FinalStatus};
11
12/// 本版本的线上版本号(与路由 `/api/v1/…` 一致)。
13pub 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    /// Development placeholder until real signing and verifier plumbing is implemented.
74    pub result_digest: String,
75    /// Development placeholder signature. Consumers must not treat this as production attestation.
76    pub signature: String,
77    /// Development placeholder issuer identity, prefixed as `dev-placeholder:...`.
78    pub issued_by: String,
79    pub attested_at: String,
80}
81
82/// Gateway 对动作结果上报的确认响应。
83#[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}