1use std::path::Path;
2
3use crate::snapshot::snapshot_lane_graph_state;
4use crate::{policy_audit_record_for_spec, snapshot_single_ring_json};
5
6pub fn incident_bundle(
17 out: &Path,
18 spec_path: &Path,
19 config_path: Option<&Path>,
20 ring_path: Option<&Path>,
21) -> Result<(), String> {
22 std::fs::create_dir_all(out).map_err(|e| format!("create out dir failed: {e}"))?;
23
24 let spec_dst = out.join("spec.kv");
25 std::fs::copy(spec_path, &spec_dst).map_err(|e| format!("copy spec failed: {e}"))?;
26
27 let spec_validation = match std::fs::read_to_string(spec_path) {
28 Ok(kv) => match byteor_pipeline_spec::decode_kv_v1(&kv)
29 .and_then(|s| byteor_pipeline_spec::validate_v1(&s).map(|_| s))
30 {
31 Ok(spec) => serde_json::json!({
32 "ok": true,
33 "description": byteor_pipeline_spec::describe_v1(&spec),
34 }),
35 Err(e) => serde_json::json!({
36 "ok": false,
37 "error": e.to_string(),
38 }),
39 },
40 Err(e) => serde_json::json!({
41 "ok": false,
42 "error": format!("read spec failed: {e}"),
43 }),
44 };
45 std::fs::write(
46 out.join("spec_validation.json"),
47 serde_json::to_vec_pretty(&spec_validation).unwrap(),
48 )
49 .map_err(|e| format!("write spec_validation.json failed: {e}"))?;
50
51 let mut policy_audit = policy_audit_record_for_spec(spec_path, "incident_bundle", None, None);
52 policy_audit["runtime_config_provided"] = serde_json::json!(config_path.is_some());
53 std::fs::write(
54 out.join("policy_audit.json"),
55 serde_json::to_vec_pretty(&policy_audit).unwrap(),
56 )
57 .map_err(|e| format!("write policy_audit.json failed: {e}"))?;
58
59 if let Some(cfg) = config_path {
60 let cfg_dst = out.join("runtime_config.txt");
61 std::fs::copy(cfg, &cfg_dst).map_err(|e| format!("copy config failed: {e}"))?;
62 }
63
64 let mut env: Vec<(String, String)> = std::env::vars().collect();
65 env.sort();
66
67 let runtime_json = serde_json::json!({
68 "crate": {
69 "name": env!("CARGO_PKG_NAME"),
70 "version": env!("CARGO_PKG_VERSION"),
71 }
72 });
73 std::fs::write(
74 out.join("runtime.json"),
75 serde_json::to_vec_pretty(&runtime_json).unwrap(),
76 )
77 .map_err(|e| format!("write runtime.json failed: {e}"))?;
78
79 let env_json = serde_json::json!({
80 "pid": std::process::id(),
81 "cwd": std::env::current_dir().ok().map(|p| p.display().to_string()),
82 "args": std::env::args().collect::<Vec<_>>(),
83 "env": env,
84 "proc_status": std::fs::read_to_string("/proc/self/status").ok(),
85 });
86 std::fs::write(
87 out.join("environment.json"),
88 serde_json::to_vec_pretty(&env_json).unwrap(),
89 )
90 .map_err(|e| format!("write environment.json failed: {e}"))?;
91
92 let decoded_spec = std::fs::read_to_string(spec_path)
93 .ok()
94 .and_then(|kv| byteor_pipeline_spec::decode_kv_v1(&kv).ok());
95
96 match decoded_spec.as_ref() {
97 Some(byteor_pipeline_spec::PipelineSpecV1::SingleRing(_)) => {
98 if let Some(ring) = ring_path {
99 let snap = snapshot_single_ring_json(ring.to_path_buf(), 4)
100 .map_err(|e| format!("snapshot failed: {e}"))?;
101 std::fs::write(
102 out.join("single_ring_snapshot.json"),
103 serde_json::to_vec_pretty(&snap).unwrap(),
104 )
105 .map_err(|e| format!("write snapshot failed: {e}"))?;
106 }
107 }
108 Some(byteor_pipeline_spec::PipelineSpecV1::LaneGraph(lg)) => {
109 let lane_graph_state = snapshot_lane_graph_state(lg, ring_path);
110 std::fs::write(
111 out.join("lane_graph_state.json"),
112 serde_json::to_vec_pretty(&lane_graph_state).unwrap(),
113 )
114 .map_err(|e| format!("write lane_graph_state.json failed: {e}"))?;
115 }
116 None => {}
117 }
118
119 Ok(())
120}