aivcs_core/
deploy_runner.rs1use chrono::{DateTime, Utc};
7use oxidized_state::{
8 ContentDigest, RunEvent, RunId, RunLedger, RunMetadata, RunSummary, StorageError,
9};
10use std::time::Instant;
11
12use crate::domain::{AivcsError, Result};
13
14#[derive(Debug, Clone)]
16pub struct DeployRunOutput {
17 pub run_id: RunId,
18 pub emitted_events: usize,
19}
20
21pub struct DeployByDigestRunner;
23
24impl DeployByDigestRunner {
25 pub async fn run(
27 ledger: &dyn RunLedger,
28 spec_digest: &ContentDigest,
29 agent_name: &str,
30 ) -> Result<DeployRunOutput> {
31 let now = Utc::now();
32 Self::run_at(ledger, spec_digest, agent_name, now).await
33 }
34
35 pub async fn run_at(
37 ledger: &dyn RunLedger,
38 spec_digest: &ContentDigest,
39 agent_name: &str,
40 timestamp: DateTime<Utc>,
41 ) -> Result<DeployRunOutput> {
42 let started = Instant::now();
43 let metadata = RunMetadata {
44 git_sha: None,
45 agent_name: agent_name.to_string(),
46 tags: serde_json::json!({
47 "mode": "deploy_by_digest",
48 }),
49 };
50
51 let run_id = ledger
52 .create_run(spec_digest, metadata)
53 .await
54 .map_err(storage_err)?;
55
56 let events = vec![
57 RunEvent {
58 seq: 1,
59 kind: "deploy_started".to_string(),
60 payload: serde_json::json!({
61 "spec_digest": spec_digest.as_str(),
62 }),
63 timestamp,
64 },
65 RunEvent {
66 seq: 2,
67 kind: "agent_executed".to_string(),
68 payload: serde_json::json!({
69 "agent_name": agent_name,
70 "spec_digest": spec_digest.as_str(),
71 }),
72 timestamp,
73 },
74 RunEvent {
75 seq: 3,
76 kind: "deploy_completed".to_string(),
77 payload: serde_json::json!({
78 "success": true,
79 }),
80 timestamp,
81 },
82 ];
83
84 for event in events {
85 ledger
86 .append_event(&run_id, event)
87 .await
88 .map_err(storage_err)?;
89 }
90
91 let summary = RunSummary {
92 total_events: 3,
93 final_state_digest: None,
96 duration_ms: started.elapsed().as_millis() as u64,
97 success: true,
98 };
99 ledger
100 .complete_run(&run_id, summary)
101 .await
102 .map_err(storage_err)?;
103
104 Ok(DeployRunOutput {
105 run_id,
106 emitted_events: 3,
107 })
108 }
109}
110
111fn storage_err(e: StorageError) -> AivcsError {
112 AivcsError::StorageError(e.to_string())
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use oxidized_state::fakes::MemoryRunLedger;
119 use oxidized_state::RunStatus;
120
121 #[tokio::test]
122 async fn deploy_run_records_spec_digest_and_completes() {
123 let ledger = MemoryRunLedger::new();
124 let digest = ContentDigest::from_bytes(b"agent-spec-v1");
125
126 let output = DeployByDigestRunner::run(&ledger, &digest, "agent-alpha")
127 .await
128 .expect("deploy run");
129
130 assert_eq!(output.emitted_events, 3);
131 let run = ledger.get_run(&output.run_id).await.expect("get run");
132 assert_eq!(run.spec_digest, digest);
133 assert_eq!(run.status, RunStatus::Completed);
134 let summary = run.summary.expect("summary");
135 assert_eq!(summary.total_events, 3);
136 assert!(summary.final_state_digest.is_none());
137 }
138}