secureops_core/check.rs
1//! The `Check` trait and the `run_audit` orchestrator.
2//!
3//! In the TS tool each audit *category* (`auditGateway`, `auditCredentials`, …)
4//! is one async function returning `AuditFinding[]`. Here that becomes one
5//! `Check` impl per category (PRODUCT.md A.4: "one Check impl per audit* fn"),
6//! living in the `secureops-checks` crate.
7
8use crate::context::AuditContext;
9use crate::scoring::{calculate_score, compute_summary, cross_layer_risk};
10use crate::types::{AuditFinding, AuditReport};
11use async_trait::async_trait;
12
13/// Options for running an audit (port of `AuditOptions`, minus `context` which
14/// is passed explicitly).
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub struct AuditOptions {
17 pub deep: bool,
18 pub fix: bool,
19 pub json: bool,
20}
21
22/// One audit category. Implementors live in `secureops-checks`.
23#[async_trait]
24pub trait Check: Send + Sync {
25 /// Stable category id (e.g. `"gateway"`), used for logging/diagnostics.
26 fn category(&self) -> &'static str;
27
28 /// Run the category against the context, returning zero or more findings.
29 ///
30 /// A check must never panic the run: the orchestrator isolates failures,
31 /// but checks should also degrade to an INFO finding on missing inputs
32 /// (mirrors the TS "the run never aborts" guarantee, PRODUCT.md B.2).
33 async fn run(&self, ctx: &dyn AuditContext, opts: &AuditOptions) -> Vec<AuditFinding>;
34}
35
36/// Run every check against `ctx`, append the MAESTRO cross-layer compound-risk
37/// finding, then score and summarize — the faithful port of `runAudit`.
38///
39/// `timestamp` is injected (RFC3339) rather than read from a clock here so this
40/// stays pure and deterministic; callers stamp `new Date().toISOString()`'s
41/// equivalent. Findings are concatenated in `checks` order, matching the fixed
42/// category order of the TS `Promise.all` aggregation.
43pub async fn run_audit(
44 ctx: &dyn AuditContext,
45 checks: &[Box<dyn Check>],
46 opts: &AuditOptions,
47 timestamp: String,
48 secureops_version: &str,
49) -> AuditReport {
50 let mut findings: Vec<AuditFinding> = Vec::new();
51 for check in checks {
52 findings.extend(check.run(ctx, opts).await);
53 }
54
55 // Cross-layer threat detection runs after all checks (PRODUCT.md B.2).
56 let cross = cross_layer_risk(&findings);
57 findings.extend(cross);
58
59 let score = calculate_score(&findings);
60 let summary = compute_summary(&findings);
61
62 AuditReport {
63 timestamp,
64 openclaw_version: ctx.openclaw_version().to_string(),
65 secureops_version: secureops_version.to_string(),
66 platform: ctx.platform().to_string(),
67 deployment_mode: ctx.deployment_mode().to_string(),
68 score,
69 findings,
70 summary,
71 }
72}