byteor_runtime/
bundle.rs

1use std::path::Path;
2
3use crate::snapshot::snapshot_lane_graph_state;
4use crate::{policy_audit_record_for_spec, snapshot_single_ring_json};
5
6/// Export an incident bundle directory.
7///
8/// Bundle contents:
9/// - `spec.kv`
10/// - `spec_validation.json`
11/// - `runtime.json`
12/// - `runtime_config.txt` (optional)
13/// - `environment.json`
14/// - `single_ring_snapshot.json` (optional for SingleRing specs)
15/// - `lane_graph_state.json` (best-effort for LaneGraph specs)
16pub 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}