Skip to main content

pidgin_lang/
expander.rs

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}