1use serde::Serialize;
2
3use crate::ast::{FieldValue, PgnPacket};
4use crate::registry::WorkflowRegistry;
5use crate::resolver::ResolvedRef;
6use crate::safety::SafetyResult;
7
8#[derive(Debug, Serialize)]
9pub struct ExpandedRef {
10 pub reference: String,
11 pub status: String,
12 pub confidence: f32,
13 pub path: Option<String>,
14}
15
16impl From<&ResolvedRef> for ExpandedRef {
17 fn from(r: &ResolvedRef) -> Self {
18 ExpandedRef {
19 reference: r.original.clone(),
20 status: format!("{:?}", r.status),
21 confidence: r.confidence,
22 path: r.resolved_path.as_ref().map(|p| p.display().to_string()),
23 }
24 }
25}
26
27#[derive(Debug, Serialize)]
28pub struct ExpandedRunPacket {
29 pub spec_version: String,
30 pub run_id: String,
31 pub workflow: String,
32 pub mode: String,
33 pub inputs: Vec<ExpandedRef>,
34 pub outputs: Vec<ExpandedRef>,
35 pub do_actions: Vec<String>,
36 pub deny_actions: Vec<String>,
37 pub effective_risk: String,
38 pub human_required: bool,
39 pub recommended_executor: String,
40 pub fallback_executor: String,
41 pub ttl: String,
42 pub note: Option<String>,
43}
44
45#[derive(Debug, Serialize)]
46pub struct ExpandedApprovalPacket {
47 pub spec_version: String,
48 pub run_id: String,
49 pub workflow: String,
50 pub risk: String,
51 pub human_required: bool,
52 pub actions: Vec<String>,
53}
54
55pub fn expand_to_run_packet(
56 packet: &PgnPacket,
57 resolved_refs: &[ResolvedRef],
58 safety: &SafetyResult,
59 workflows: &WorkflowRegistry,
60) -> ExpandedRunPacket {
61 let wf_name = packet
62 .fields
63 .get("wf")
64 .and_then(|v| match v {
65 FieldValue::Scalar(s) => Some(s.as_str()),
66 _ => None,
67 })
68 .unwrap_or("unknown");
69
70 let workflow_entry = workflows.workflows.get(wf_name);
71
72 let inputs: Vec<ExpandedRef> = resolved_refs
73 .iter()
74 .filter(|r| matches!(packet.fields.get("in"), Some(FieldValue::List(refs)) if refs.contains(&r.original)))
75 .map(ExpandedRef::from)
76 .collect();
77
78 let outputs: Vec<ExpandedRef> = resolved_refs
79 .iter()
80 .filter(|r| matches!(packet.fields.get("out"), Some(FieldValue::List(refs)) if refs.contains(&r.original)))
81 .map(ExpandedRef::from)
82 .collect();
83
84 let do_actions = packet
85 .fields
86 .get("do")
87 .and_then(|v| match v {
88 FieldValue::List(items) => Some(items.clone()),
89 _ => None,
90 })
91 .unwrap_or_default();
92
93 let deny_actions = packet
94 .fields
95 .get("deny")
96 .and_then(|v| match v {
97 FieldValue::List(items) => Some(items.clone()),
98 _ => None,
99 })
100 .unwrap_or_default();
101
102 let note = packet
103 .fields
104 .get("note")
105 .and_then(|v| match v {
106 FieldValue::Scalar(s) => Some(s.clone()),
107 _ => None,
108 });
109
110 let recommended_executor = workflow_entry
111 .map(|w| w.recommended_executor.clone())
112 .unwrap_or_else(|| "unknown".to_string());
113
114 let fallback_executor = workflow_entry
115 .map(|w| w.fallback_executor.clone())
116 .unwrap_or_else(|| "unknown".to_string());
117
118 let ttl = packet
119 .fields
120 .get("ttl")
121 .and_then(|v| match v {
122 FieldValue::Scalar(s) => Some(s.clone()),
123 _ => None,
124 })
125 .unwrap_or_else(|| "24h".to_string());
126
127 ExpandedRunPacket {
128 spec_version: "1.0".to_string(),
129 run_id: packet.run_id.clone(),
130 workflow: wf_name.to_string(),
131 mode: packet
132 .fields
133 .get("mode")
134 .and_then(|v| match v {
135 FieldValue::Scalar(s) => Some(s.clone()),
136 _ => None,
137 })
138 .unwrap_or_else(|| "unknown".to_string()),
139 inputs,
140 outputs,
141 do_actions,
142 deny_actions,
143 effective_risk: safety.effective_risk.clone(),
144 human_required: safety.human_required,
145 recommended_executor,
146 fallback_executor,
147 ttl,
148 note,
149 }
150}
151
152pub fn expand_to_approval_request(
153 packet: &PgnPacket,
154 safety: &SafetyResult,
155 _workflows: &WorkflowRegistry,
156) -> ExpandedApprovalPacket {
157 let wf_name = packet
158 .fields
159 .get("wf")
160 .and_then(|v| match v {
161 FieldValue::Scalar(s) => Some(s.as_str()),
162 _ => None,
163 })
164 .unwrap_or("unknown");
165
166 let actions = packet
167 .fields
168 .get("do")
169 .and_then(|v| match v {
170 FieldValue::List(items) => Some(items.clone()),
171 _ => None,
172 })
173 .unwrap_or_default();
174
175 ExpandedApprovalPacket {
176 spec_version: "1.0".to_string(),
177 run_id: packet.run_id.clone(),
178 workflow: wf_name.to_string(),
179 risk: safety.effective_risk.clone(),
180 human_required: safety.human_required,
181 actions,
182 }
183}