From 36fbcb1d637e56dd818ed0c7798cb6ea1e35dfa2 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Thu, 4 Jun 2026 23:33:40 +0300 Subject: [PATCH] fix(portal): keep technical health out of security view --- adk-rust/crates/detmir-portal/src/main.rs | 60 +++++++++++-------- .../crates/detmir-portal/src/static/app.js | 2 +- scripts/detmir-portal-tabs-smoke.mjs | 8 +++ 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/adk-rust/crates/detmir-portal/src/main.rs b/adk-rust/crates/detmir-portal/src/main.rs index 02ab667..f412dbc 100644 --- a/adk-rust/crates/detmir-portal/src/main.rs +++ b/adk-rust/crates/detmir-portal/src/main.rs @@ -2240,33 +2240,30 @@ fn agent_quality_explain(quality: &AgentQuality) -> AgentQualityExplain { fn command_json_source(name: &str, command: &str, timeout: Duration) -> SourceStatus { match run_shell(command, timeout) { - Ok((stdout, stderr, success)) => { - if !success { - return SourceStatus { - ok: false, - status: "FAIL".to_string(), - summary: format!("{name} command returned non-zero status"), - error: Some(stderr.trim().to_string()), - payload: None, - }; - } - match serde_json::from_str::(&stdout) { - Ok(payload) => SourceStatus { - ok: payload_bool(&payload, "/ok").unwrap_or(true), - status: status_from_payload(&payload), - summary: source_summary(name, &payload), - error: None, - payload: Some(payload), + Ok((stdout, stderr, success)) => match serde_json::from_str::(&stdout) { + Ok(payload) => SourceStatus { + ok: payload_bool(&payload, "/ok").unwrap_or(true), + status: status_from_payload(&payload), + summary: source_summary(name, &payload), + error: if success || stderr.trim().is_empty() { + None + } else { + Some(stderr.trim().to_string()) }, - Err(err) => SourceStatus { - ok: false, - status: "FAIL".to_string(), - summary: format!("{name} returned invalid JSON"), - error: Some(err.to_string()), - payload: None, + payload: Some(payload), + }, + Err(err) => SourceStatus { + ok: false, + status: "FAIL".to_string(), + summary: if success { + format!("{name} returned invalid JSON") + } else { + format!("{name} command returned non-zero status") }, - } - } + error: Some(err.to_string()), + payload: None, + }, + }, Err(err) => SourceStatus { ok: false, status: "FAIL".to_string(), @@ -9352,6 +9349,19 @@ mod tests { assert!(err.to_string().contains("command timed out")); } + #[test] + fn command_json_source_accepts_valid_json_with_nonzero_exit() { + let source = command_json_source( + "detmir_status", + "printf '%s' '{\"severity\":\"OK\",\"ok_for_operator\":false}'; exit 2", + Duration::from_secs(1), + ); + assert!(source.ok); + assert_eq!(source.status, "OK"); + assert!(source.payload.is_some()); + assert!(!source.summary.contains("command returned non-zero")); + } + #[test] fn agent_quality_history_counts_seven_ok_days() { let (_dir, path) = write_history( diff --git a/adk-rust/crates/detmir-portal/src/static/app.js b/adk-rust/crates/detmir-portal/src/static/app.js index d557124..e6db6b8 100644 --- a/adk-rust/crates/detmir-portal/src/static/app.js +++ b/adk-rust/crates/detmir-portal/src/static/app.js @@ -1214,7 +1214,7 @@ function renderSecurityView(data, report, extras = {}) { ${renderInvestigationPacks(report?.risk_incident_candidates)}

Расследования

ручная проверка, решения и материалы
- ${renderIncidentsList(extras.incidents || data.incidents || [])} + ${renderDlpIncidentsList(extras.incidents || data.incidents || [])}
`; } diff --git a/scripts/detmir-portal-tabs-smoke.mjs b/scripts/detmir-portal-tabs-smoke.mjs index 1afe3bb..eb69e2b 100644 --- a/scripts/detmir-portal-tabs-smoke.mjs +++ b/scripts/detmir-portal-tabs-smoke.mjs @@ -289,6 +289,14 @@ async function main() { "Связь рисков и активности", ].every((marker) => containsText(securityText, marker)), }); + checks.push({ + name: "security_role_hides_technical_health_sources", + ok: + !containsText(securityText, "detmir_check") + && !containsText(securityText, "detmir_status") + && !containsText(securityText, "command failed") + && !containsText(securityText, "command returned non-zero"), + }); checks.push({ name: "security_events_security_text", ok: !expectedSecurityText || containsText(securityText, expectedSecurityText),