bijux-dag-runtime 0.4.0

Execution engine, replay semantics, and runtime policy layer for Bijux DAG graphs.
Documentation
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretReference {
    pub secret_id: String,
    pub version: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecretSource {
    LocalDev,
    FilePath { path: String },
    Environment { key: String },
    ExternalManager { provider: String, path: String },
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecretResolutionTiming {
    RuntimeOnly,
    CompileTimeAllowed,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretUsageAuditEvent {
    pub secret_id: String,
    pub node_id: String,
    pub run_id: String,
    pub unix_ms: u128,
    pub access_mode: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretMaskingPolicy {
    pub redact_logs: bool,
    pub redact_diagnostics: bool,
    pub redact_manifests: bool,
    pub redact_exports: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretScopeRule {
    pub tenant_id: Option<String>,
    pub dag_id: Option<String>,
    pub run_id: Option<String>,
    pub node_id: Option<String>,
    pub worker_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SecretInjectionMode {
    Env,
    FileMount,
    BackendNative,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretDeliveryPolicy {
    pub allowed_modes: Vec<SecretInjectionMode>,
    pub deny_process_args: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretRotationRule {
    pub allow_latest: bool,
    pub require_pin_for_backfill: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretVersionSelection {
    pub selected_version: String,
    pub pinned: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretArtifactPolicy {
    pub allow_secret_artifacts: bool,
    pub required_classification: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretTaintRecord {
    pub node_id: String,
    pub tainted_logs: bool,
    pub tainted_diagnostics: bool,
    pub tainted_artifacts: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SensitiveArtifactClass {
    SecretDerived,
    Regulated,
    InternalRestricted,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SensitiveArtifactRestriction {
    pub class: SensitiveArtifactClass,
    pub min_retention_days: u32,
    pub export_requires_approval: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecureWorkspaceRule {
    pub secure_temp_cleanup: bool,
    pub remove_secret_mounts_on_exit: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecureTeardownPolicy {
    pub wipe_env_on_cancel: bool,
    pub wipe_files_on_cancel: bool,
    pub teardown_timeout_ms: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecureExecutionMode {
    pub enabled: bool,
    pub environment: String,
    pub strict_policy_bundle: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretIntegrationReadiness {
    pub source_connected: bool,
    pub masking_enabled: bool,
    pub audit_enabled: bool,
    pub strict_mode_supported: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretLeakIncident {
    pub incident_id: String,
    pub detected_in: String,
    pub run_id: Option<String>,
    pub containment_actions: Vec<String>,
}

pub fn secret_scope_allows(scope: &SecretScopeRule, requested: &SecretScopeRule) -> bool {
    let field_match = |a: &Option<String>, b: &Option<String>| a.is_none() || a == b;
    field_match(&scope.tenant_id, &requested.tenant_id)
        && field_match(&scope.dag_id, &requested.dag_id)
        && field_match(&scope.run_id, &requested.run_id)
        && field_match(&scope.node_id, &requested.node_id)
        && field_match(&scope.worker_id, &requested.worker_id)
}

pub fn validate_secret_delivery_mode(
    mode: &SecretInjectionMode,
    policy: &SecretDeliveryPolicy,
) -> bool {
    policy.allowed_modes.iter().any(|m| m == mode)
}

pub fn select_secret_version(
    available_versions: &[String],
    pinned_version: Option<&str>,
    rotation: &SecretRotationRule,
    is_backfill: bool,
) -> Option<SecretVersionSelection> {
    if let Some(pin) = pinned_version {
        if available_versions.iter().any(|v| v == pin) {
            return Some(SecretVersionSelection {
                selected_version: pin.to_string(),
                pinned: true,
            });
        }
        return None;
    }
    if is_backfill && rotation.require_pin_for_backfill {
        return None;
    }
    if rotation.allow_latest {
        let latest = available_versions.iter().max()?.clone();
        return Some(SecretVersionSelection { selected_version: latest, pinned: false });
    }
    None
}

pub fn should_materialize_secret_artifact(
    policy: &SecretArtifactPolicy,
    classification: Option<&str>,
) -> bool {
    policy.allow_secret_artifacts
        && policy
            .required_classification
            .as_ref()
            .map(|required| Some(required.as_str()) == classification)
            .unwrap_or(true)
}

pub fn redact_secret_payload(payload: &str, secrets: &[String]) -> String {
    let mut redacted = payload.to_string();
    for secret in secrets {
        redacted = redacted.replace(secret, "***REDACTED***");
    }
    redacted
}

pub fn taint_from_secret_usage(
    used_secret: bool,
    produced_artifacts: bool,
    produced_logs: bool,
) -> SecretTaintRecord {
    SecretTaintRecord {
        node_id: "unknown".to_string(),
        tainted_logs: used_secret && produced_logs,
        tainted_diagnostics: used_secret,
        tainted_artifacts: used_secret && produced_artifacts,
    }
}

pub fn secure_mode_effective(environment: &str, mode: &SecureExecutionMode) -> bool {
    mode.enabled && mode.environment == environment
}

pub fn leak_conformance_check(outputs: &[String]) -> bool {
    !outputs.iter().any(|line| {
        let l = line.to_ascii_lowercase();
        l.contains("secret=") || l.contains("token=") || l.contains("password=")
    })
}

pub fn secret_readiness(
    sources: &[SecretSource],
    masking_policy: &SecretMaskingPolicy,
    audit_events: &[SecretUsageAuditEvent],
    secure_mode_supported: bool,
) -> SecretIntegrationReadiness {
    SecretIntegrationReadiness {
        source_connected: !sources.is_empty(),
        masking_enabled: masking_policy.redact_logs
            && masking_policy.redact_diagnostics
            && masking_policy.redact_manifests
            && masking_policy.redact_exports,
        audit_enabled: !audit_events.is_empty(),
        strict_mode_supported: secure_mode_supported,
    }
}

pub fn incident_response_actions(incident: &SecretLeakIncident) -> BTreeSet<String> {
    incident.containment_actions.iter().cloned().collect::<BTreeSet<_>>()
}

pub fn secure_cleanup_required(
    rule: &SecureWorkspaceRule,
    teardown: &SecureTeardownPolicy,
) -> bool {
    rule.secure_temp_cleanup
        && rule.remove_secret_mounts_on_exit
        && teardown.wipe_env_on_cancel
        && teardown.wipe_files_on_cancel
}

pub fn summarize_sensitive_classes(
    restrictions: &[SensitiveArtifactRestriction],
) -> BTreeMap<String, (u32, bool)> {
    let mut map = BTreeMap::new();
    for r in restrictions {
        map.insert(format!("{:?}", r.class), (r.min_retention_days, r.export_requires_approval));
    }
    map
}