greentic_operator/
qa_flow_handler.rs1use std::path::Path;
8
9use anyhow::Result;
10use serde_json::{Value, json};
11
12use crate::component_qa_ops::{self, QaMode};
13use crate::demo::runner_host::OperatorContext;
14use crate::domains::{Domain, ProviderPack};
15use crate::qa_setup_wizard;
16use crate::setup_to_formspec;
17
18pub fn is_qa_process_node(component_id: &str) -> bool {
20 component_id == "component-qa"
21 || component_id == "ai.greentic.component-qa"
22 || component_id.ends_with("/component-qa")
23 || component_id.contains("qa.process")
24}
25
26pub fn handle_qa_process_node(
35 root: &Path,
36 node_config: &Value,
37 provider_pack: &ProviderPack,
38 provider_id: &str,
39 domain: Domain,
40 ctx: &OperatorContext,
41 interactive: bool,
42) -> Result<Value> {
43 let mode_str = node_config
45 .get("mode")
46 .and_then(Value::as_str)
47 .unwrap_or("setup");
48 let mode = match mode_str {
49 "remove" => QaMode::Remove,
50 "upgrade" => QaMode::Upgrade,
51 "default" => QaMode::Default,
52 _ => QaMode::Setup,
53 };
54
55 let supplied_answers = node_config.get("answers").cloned();
57
58 let form_spec = setup_to_formspec::pack_to_form_spec(&provider_pack.path, provider_id);
60
61 let answers = if let Some(answers) = supplied_answers {
63 if let Some(ref spec) = form_spec {
65 qa_setup_wizard::validate_answers_against_form_spec(spec, &answers)?;
66 }
67 answers
68 } else if interactive {
69 let (answers, _spec) = qa_setup_wizard::run_qa_setup(
71 &provider_pack.path,
72 provider_id,
73 None,
74 true,
75 form_spec.clone(),
76 )?;
77 answers
78 } else {
79 json!({})
80 };
81
82 let providers_root = root
84 .join("state")
85 .join("runtime")
86 .join(&ctx.tenant)
87 .join("providers");
88 let current_config = crate::provider_config_envelope::read_provider_config_envelope(
89 &providers_root,
90 provider_id,
91 )?
92 .map(|envelope| envelope.config);
93
94 match component_qa_ops::apply_answers_via_component_qa(
96 root,
97 domain,
98 &ctx.tenant,
99 ctx.team.as_deref(),
100 provider_pack,
101 provider_id,
102 mode,
103 current_config.as_ref(),
104 &answers,
105 ) {
106 Ok(Some(config)) => Ok(json!({
107 "status": "ok",
108 "config": config,
109 "mode": mode_str,
110 "provider": provider_id,
111 })),
112 Ok(None) => Ok(json!({
113 "status": "skip",
114 "reason": "provider does not support QA contract",
115 "provider": provider_id,
116 })),
117 Err(diag) => Ok(json!({
118 "status": "error",
119 "code": diag.code.as_str(),
120 "message": diag.message,
121 "provider": provider_id,
122 })),
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn detects_qa_process_node() {
132 assert!(is_qa_process_node("component-qa"));
133 assert!(is_qa_process_node("ai.greentic.component-qa"));
134 assert!(is_qa_process_node("root:component/component-qa"));
135 assert!(is_qa_process_node("qa.process.setup"));
136 assert!(!is_qa_process_node("component-llm-openai"));
137 assert!(!is_qa_process_node("messaging-telegram"));
138 }
139}