use anyhow::Result; use serde::Serialize; use serde_json::{json, Value}; pub(crate) fn emit_report(report: &T, json_output: bool) -> Result<()> { let mut value = serde_json::to_value(report)?; let exit_code = apply_command_report_exit_code(&mut value); if json_output { println!("{}", serde_json::to_string_pretty(&value)?); } else { println!("{}", human_report(&value)); } if let Some(exit_code) = exit_code { std::process::exit(exit_code); } Ok(()) } pub(crate) fn human_report(value: &Value) -> String { let mut lines = Vec::new(); let title = value .get("command") .and_then(Value::as_str) .map(|command| format!("Clusterflux {command}")) .or_else(|| { if value.get("human_flow").is_some() { Some("Clusterflux login".to_owned()) } else if value.get("metadata").is_some() && value.get("source_provider_manifest").is_some() { Some("Clusterflux bundle inspect".to_owned()) } else if value.get("entry").is_some() && value.get("session").is_some() { Some("Clusterflux run".to_owned()) } else if value.get("capabilities").is_some() && value.get("node").is_some() { Some("Clusterflux node attach".to_owned()) } else if value.get("public_key_fingerprint").is_some() { Some("Clusterflux agent enroll".to_owned()) } else { None } }) .unwrap_or_else(|| "Clusterflux report".to_owned()); lines.push(title); push_string_field(&mut lines, value, "status", "status"); push_string_field(&mut lines, value, "coordinator", "coordinator"); push_string_field(&mut lines, value, "active_coordinator", "coordinator"); push_string_field(&mut lines, value, "tenant", "tenant"); push_string_field(&mut lines, value, "project", "project"); push_string_field(&mut lines, value, "process", "process"); push_string_field(&mut lines, value, "active_process", "active process"); push_string_field(&mut lines, value, "node", "node"); push_string_field(&mut lines, value, "artifact", "artifact"); push_string_field(&mut lines, value, "entry", "entry"); push_string_field(&mut lines, value, "quota_tier", "quota tier"); push_string_field(&mut lines, value, "config_file", "config"); push_string_field(&mut lines, value, "adapter", "adapter"); if let Some(project) = value.get("project").and_then(Value::as_str) { if !lines .iter() .any(|line| line == &format!("project: {project}")) { lines.push(format!("project: {project}")); } } if let Some(project_config) = value.get("project_config").or_else(|| value.get("project")) { if project_config.is_object() { push_nested_string_field(&mut lines, project_config, "tenant", "tenant"); push_nested_string_field(&mut lines, project_config, "project", "project"); push_nested_string_field(&mut lines, project_config, "coordinator", "coordinator"); } } if let Some(link) = value.get("current_directory_link") { if link .get("links_current_directory") .and_then(Value::as_bool) .unwrap_or(false) { lines.push("current directory linked: true".to_owned()); } push_nested_string_field(&mut lines, link, "config_file", "current directory config"); } if let Some(metadata) = value.get("metadata") { push_nested_string_field(&mut lines, metadata, "identity", "bundle"); if let Some(environments) = metadata.get("environments").and_then(Value::as_array) { let names = environments .iter() .filter_map(|env| env.get("name").and_then(Value::as_str)) .collect::>(); if !names.is_empty() { lines.push(format!("environments: {}", names.join(", "))); } } } if let Some(bundle) = value.get("bundle") { if let Some(metadata) = bundle.get("metadata") { push_nested_string_field(&mut lines, metadata, "identity", "bundle"); } } if let Some(environments) = value .get("discovered_environments") .and_then(Value::as_array) { let names = environments .iter() .filter_map(Value::as_str) .collect::>(); if !names.is_empty() { lines.push(format!("environments: {}", names.join(", "))); } } if let Some(attached_nodes) = value.get("attached_nodes") { if let Some(count) = attached_nodes.get("count").and_then(Value::as_u64) { let online = attached_nodes .get("online") .and_then(Value::as_u64) .unwrap_or(0); lines.push(format!("attached nodes: {count} ({online} online)")); } } if let Some(current_usage) = value.pointer("/quota_posture/current_usage") { lines.push(format!("quota usage: {}", compact_json(current_usage))); } if let Some(current_task_count) = value.get("current_task_count").and_then(Value::as_u64) { lines.push(format!("tasks: {current_task_count}")); } if let Some(tasks) = value.get("current_tasks").and_then(Value::as_array) { push_task_placement_reasons(&mut lines, tasks); push_task_locality_failures(&mut lines, tasks); } if let Some(tasks) = value.get("tasks").and_then(Value::as_array) { lines.push(format!("tasks: {}", tasks.len())); push_task_placement_reasons(&mut lines, tasks); push_task_locality_failures(&mut lines, tasks); } if let Some(log_entries) = value.get("log_entries").and_then(Value::as_array) { lines.push(format!("log entries: {}", log_entries.len())); } if let Some(artifacts) = value.get("artifacts").and_then(Value::as_array) { lines.push(format!("artifacts: {}", artifacts.len())); } if let Some(statuses) = value .get("source_provider_statuses") .and_then(Value::as_array) { push_source_provider_statuses(&mut lines, statuses); } if let Some(bundle_statuses) = value .pointer("/bundle/source_provider_statuses") .and_then(Value::as_array) { push_source_provider_statuses(&mut lines, bundle_statuses); } if let Some(diagnostics) = value .get("pre_schedule_diagnostics") .or_else(|| value.get("diagnostics")) .and_then(Value::as_array) { push_cli_diagnostics(&mut lines, diagnostics); } if let Some(bundle_diagnostics) = value .pointer("/bundle/pre_schedule_diagnostics") .and_then(Value::as_array) { push_cli_diagnostics(&mut lines, bundle_diagnostics); } if let Some(current_usage) = value.get("current_usage") { lines.push(format!("quota usage: {}", compact_json(current_usage))); } if let Some(next_blocked_action) = value.get("next_blocked_action") { if !next_blocked_action.is_null() { lines.push(format!( "next blocked action: {}", compact_json(next_blocked_action) )); } } push_machine_error_line(&mut lines, value.get("machine_error")); push_machine_error_line(&mut lines, value.pointer("/run_start/machine_error")); push_machine_error_line(&mut lines, value.pointer("/download_session/machine_error")); push_machine_error_line(&mut lines, value.pointer("/export_plan/machine_error")); push_machine_error_line(&mut lines, value.pointer("/local_export/machine_error")); push_machine_error_line( &mut lines, value.pointer("/local_export/download_session/machine_error"), ); push_machine_error_line( &mut lines, value.pointer("/local_export/stream/machine_error"), ); if let Some(flow) = value.get("human_flow") { if let Some(browser) = flow.get("Browser") { lines.push("flow: browser".to_owned()); push_nested_string_field(&mut lines, browser, "authorization_url", "open"); } } if let Some(session) = value.get("session") { lines.push(format!("session: {}", compact_json(session))); } if let Some(account) = value.get("coordinator_account_status") { if let Some(checked) = account.get("checked").and_then(Value::as_bool) { lines.push(format!("account status checked: {checked}")); } push_nested_string_field(&mut lines, account, "account_status", "account status"); if let Some(suspended) = account.get("suspended").and_then(Value::as_bool) { lines.push(format!("account suspended: {suspended}")); } if let Some(disabled) = account.get("disabled").and_then(Value::as_bool) { lines.push(format!("account disabled: {disabled}")); } if let Some(deleted) = account.get("deleted").and_then(Value::as_bool) { lines.push(format!("account deleted: {deleted}")); } if let Some(manual_review) = account.get("manual_review").and_then(Value::as_bool) { lines.push(format!("account manual review: {manual_review}")); } push_nested_string_field(&mut lines, account, "sanitized_reason", "account reason"); if let Some(exposed) = account .get("private_moderation_details_exposed") .and_then(Value::as_bool) { lines.push(format!("private moderation details exposed: {exposed}")); } } if let Some(coordinator_selection) = value.get("coordinator") { if coordinator_selection.is_object() { lines.push(format!( "coordinator: {}", compact_json(coordinator_selection) )); } } if let Some(reachability) = value.get("coordinator_reachability") { if let Some(status) = reachability.get("status").and_then(Value::as_str) { lines.push(format!("coordinator reachability: {status}")); } if let Some(error) = reachability.get("error").and_then(Value::as_str) { lines.push(format!("coordinator error: {error}")); } if let Some(response_type) = reachability .pointer("/response/type") .and_then(Value::as_str) { lines.push(format!("coordinator ping: {response_type}")); } } if let Some(response) = value .get("response") .or_else(|| value.get("coordinator_response")) { if let Some(response_type) = response.get("type").and_then(Value::as_str) { lines.push(format!("coordinator response: {response_type}")); } } if let Some(events) = value .pointer("/events/response/events") .and_then(Value::as_array) { lines.push(format!("events: {}", events.len())); } if let Some(events) = value .pointer("/coordinator_response/response/events") .and_then(Value::as_array) { lines.push(format!("events: {}", events.len())); } if let Some(requires_confirmation) = value.get("requires_confirmation").and_then(Value::as_bool) { lines.push(format!( "confirmation: {}", if requires_confirmation { "required" } else { "confirmed" } )); } if let Some(flag) = value .get("private_website_required") .and_then(Value::as_bool) { lines.push(format!("private website required: {flag}")); } if let Some(next_actions) = value.get("next_actions").and_then(Value::as_array) { let actions = next_actions .iter() .filter_map(Value::as_str) .collect::>(); if !actions.is_empty() { lines.push(format!("next: {}", actions.join("; "))); } } if let Some(auth) = value.get("auth").or_else(|| value.get("session")) { if let Some(kind) = auth.get("kind").and_then(Value::as_str) { lines.push(format!("auth: {kind}")); } if let Some(authenticated) = auth.get("authenticated").and_then(Value::as_bool) { lines.push(format!("authenticated: {authenticated}")); } if let Some(expires) = auth.get("expires_at").and_then(Value::as_str) { lines.push(format!("token expiry: {expires}")); } else if let Some(posture) = auth.get("token_expiry_posture").and_then(Value::as_str) { lines.push(format!("token expiry: {posture}")); } else if auth.get("authenticated").is_some() { lines.push("token expiry: unavailable".to_owned()); } } if let Some(dependencies) = value.get("dependencies").and_then(Value::as_object) { let mut entries = dependencies .iter() .map(|(name, available)| { format!( "{name}={}", if available.as_bool().unwrap_or(false) { "ok" } else { "missing" } ) }) .collect::>(); entries.sort(); if !entries.is_empty() { lines.push(format!("dependencies: {}", entries.join(", "))); } } if let Some(readiness) = value.get("node_readiness") { push_nested_string_field(&mut lines, readiness, "os", "node os"); push_nested_string_field(&mut lines, readiness, "arch", "node arch"); if let Some(capabilities) = readiness.get("capabilities").and_then(Value::as_array) { let caps = capabilities .iter() .filter_map(Value::as_str) .collect::>(); if !caps.is_empty() { lines.push(format!("node capabilities: {}", caps.join(", "))); } } } if let Some(summary) = value.get("node_readiness_summary") { push_nested_string_field(&mut lines, summary, "status", "node readiness"); if let Some(missing) = summary .get("missing_local_dependencies") .and_then(Value::as_array) { let missing = missing.iter().filter_map(Value::as_str).collect::>(); if !missing.is_empty() { lines.push(format!("node missing dependencies: {}", missing.join(", "))); } } if let Some(actions) = summary.get("next_actions").and_then(Value::as_array) { let actions = actions.iter().filter_map(Value::as_str).collect::>(); if !actions.is_empty() { lines.push(format!("node next: {}", actions.join("; "))); } } } if let Some(detection) = value .pointer("/plan/detection") .or_else(|| value.get("detection")) { push_node_attach_detection(&mut lines, detection); } if let Some(disclosures) = value .get("grant_disclosures") .and_then(Value::as_array) .filter(|disclosures| !disclosures.is_empty()) { for disclosure in disclosures { let grant = disclosure .get("grant") .and_then(Value::as_str) .unwrap_or("capability"); let description = disclosure .get("description") .and_then(Value::as_str) .unwrap_or("capability grant"); let policy = if disclosure .get("coordinator_policy_limited") .and_then(Value::as_bool) .unwrap_or(false) { "policy-limited" } else { "unbounded" }; lines.push(format!("grant {grant}: {description} ({policy})")); } } lines.dedup(); lines.join("\n") } fn push_node_attach_detection(lines: &mut Vec, detection: &Value) { push_nested_string_field(lines, detection, "os", "node os"); push_nested_string_field(lines, detection, "arch", "node arch"); push_nested_string_field(lines, detection, "command_backend", "command backend"); if let Some(backend) = detection.get("container_backend").and_then(Value::as_str) { let available = detection .get("container_backend_available") .and_then(Value::as_bool) .unwrap_or(false); lines.push(format!( "container backend: {backend} ({})", if available { "available" } else { "reported" } )); } else if detection .get("container_backend_reported") .and_then(Value::as_bool) .unwrap_or(false) { lines.push("container backend: reported".to_owned()); } if let Some(providers) = detection .get("source_provider_backends") .and_then(Value::as_array) { let entries = providers .iter() .filter_map(|provider| { let name = provider.get("provider").and_then(Value::as_str)?; let state = if provider .get("available") .and_then(Value::as_bool) .unwrap_or(false) { "available" } else { "detected" }; Some(format!("{name}={state}")) }) .collect::>(); if !entries.is_empty() { lines.push(format!("source providers: {}", entries.join(", "))); } } if let Some(overrides) = detection .get("manual_capability_overrides") .and_then(Value::as_array) { let overrides = overrides .iter() .filter_map(Value::as_str) .collect::>(); if !overrides.is_empty() { lines.push(format!("capability overrides: {}", overrides.join(", "))); } } } fn push_string_field(lines: &mut Vec, value: &Value, key: &str, label: &str) { if let Some(text) = value.get(key).and_then(Value::as_str) { lines.push(format!("{label}: {text}")); } else if let Some(path) = value.get(key).and_then(|value| { value .as_object() .and_then(|object| object.get("display")) .and_then(Value::as_str) }) { lines.push(format!("{label}: {path}")); } } fn push_task_placement_reasons(lines: &mut Vec, tasks: &[Value]) { for task in tasks { let Some(placement) = task.get("node_placement") else { continue; }; let Some(reasons) = placement.get("reasons").and_then(Value::as_array) else { continue; }; let reasons = reasons.iter().filter_map(Value::as_str).collect::>(); if reasons.is_empty() { continue; } let task_name = task .get("task") .and_then(Value::as_str) .unwrap_or("unknown"); let node = placement .get("node") .and_then(Value::as_str) .unwrap_or("unknown"); lines.push(format!( "placement {task_name}: {node} ({})", reasons.join(", ") )); } } fn push_task_locality_failures(lines: &mut Vec, tasks: &[Value]) { for task in tasks { let Some(locality) = task.get("locality_failure") else { continue; }; if locality.is_null() { continue; } let task_name = task .get("task") .and_then(Value::as_str) .unwrap_or("unknown"); let affected = locality .get("affected_data") .and_then(Value::as_str) .unwrap_or("direct_transfer"); let reason = locality .get("reason") .and_then(Value::as_str) .unwrap_or("direct transfer or locality failed"); lines.push(format!("locality {task_name}: {affected} ({reason})")); let actions = locality .get("safe_next_actions") .and_then(Value::as_array) .map(|actions| actions.iter().filter_map(Value::as_str).collect::>()) .unwrap_or_default(); if !actions.is_empty() { lines.push(format!("locality next {task_name}: {}", actions.join("; "))); } } } fn push_nested_string_field(lines: &mut Vec, value: &Value, key: &str, label: &str) { if let Some(text) = value.get(key).and_then(Value::as_str) { lines.push(format!("{label}: {text}")); } } fn push_machine_error_line(lines: &mut Vec, machine_error: Option<&Value>) { let Some(machine_error) = machine_error else { return; }; if machine_error.is_null() { return; } let category = machine_error .get("category") .and_then(Value::as_str) .unwrap_or("unknown"); let exit_code = machine_error .get("stable_exit_code") .and_then(Value::as_i64) .unwrap_or(1); lines.push(format!("error category: {category} (exit {exit_code})")); if let Some(tier) = machine_error .get("community_tier_label") .and_then(Value::as_str) { lines.push(format!("quota tier: {tier}")); } if let Some(next_actions) = machine_error.get("next_actions").and_then(Value::as_array) { let actions = next_actions .iter() .filter_map(Value::as_str) .collect::>(); if !actions.is_empty() { lines.push(format!("error next: {}", actions.join("; "))); } } } fn push_source_provider_statuses(lines: &mut Vec, statuses: &[Value]) { let entries = statuses .iter() .filter_map(|status| { let provider = status.get("provider").and_then(Value::as_str)?; let state = status.get("status").and_then(Value::as_str)?; let active = status .get("active") .and_then(Value::as_bool) .unwrap_or(false); Some(format!( "{provider}={state}{}", if active { "(active)" } else { "" } )) }) .collect::>(); if !entries.is_empty() { lines.push(format!("source providers: {}", entries.join(", "))); } } fn push_cli_diagnostics(lines: &mut Vec, diagnostics: &[Value]) { if diagnostics.is_empty() { return; } lines.push(format!("diagnostics: {}", diagnostics.len())); for diagnostic in diagnostics.iter().take(3) { let severity = diagnostic .get("severity") .and_then(Value::as_str) .unwrap_or("info"); let message = diagnostic .get("message") .and_then(Value::as_str) .unwrap_or("diagnostic"); lines.push(format!("diagnostic {severity}: {message}")); } } fn compact_json(value: &Value) -> String { serde_json::to_string(value).unwrap_or_else(|_| "".to_owned()) } pub(crate) fn apply_command_report_exit_code(value: &mut Value) -> Option { for pointer in [ "/machine_error", "/run_start/machine_error", "/restart_request/machine_error", "/cancel_request/machine_error", "/task_restart/machine_error", "/download_session/machine_error", "/export_plan/machine_error", "/local_export/machine_error", "/local_export/download_session/machine_error", "/local_export/stream/machine_error", ] { let Some(machine_error) = value.pointer_mut(pointer) else { continue; }; let Some(exit_code) = machine_error .get("stable_exit_code") .and_then(Value::as_i64) .and_then(|code| i32::try_from(code).ok()) .filter(|code| *code != 0) else { continue; }; if let Some(object) = machine_error.as_object_mut() { object.insert("process_exit_code_applied".to_owned(), json!(true)); } return Some(exit_code); } None }