bijux-dag-runtime 0.4.0

Execution engine, replay semantics, and runtime policy layer for Bijux DAG graphs.
Documentation
use crate::{
    transition_cause_for_failure, transition_cause_for_skip_reason, transition_cause_for_status,
    FailureInfo, NodeStatus, PolicyConfig, RuntimeConfig, Selector, SelectorSet,
};

#[test]
fn runtime_config_default_is_execution_independent() {
    let a = RuntimeConfig::default();
    let b = RuntimeConfig::default();
    assert_eq!(a.jobs, b.jobs);
    assert_eq!(a.cache_dir, b.cache_dir);
    assert_eq!(a.policy.clean_env, b.policy.clean_env);
}

#[test]
fn selector_set_is_deterministic_value_model() {
    let set = SelectorSet {
        include: vec![Selector::Kind("shell".to_string()), Selector::Tag("etl".to_string())],
        exclude: vec![Selector::IdPrefix("tmp_".to_string())],
    };
    let set2 = set.clone();
    assert_eq!(set.include.len(), set2.include.len());
    assert_eq!(set.exclude.len(), set2.exclude.len());
}

#[test]
fn policy_defaults_do_not_require_execution() {
    let policy = PolicyConfig::default();
    assert!(!policy.deny_network);
    assert!(!policy.deny_env);
    assert!(!policy.deny_clock);
    assert!(policy.clean_env);
}

#[test]
fn transition_cause_mapping_is_stable() {
    assert_eq!(transition_cause_for_status(&NodeStatus::Success), "ExecutionSucceeded");
    assert_eq!(transition_cause_for_status(&NodeStatus::Failed), "ExecutionFailed");
    assert_eq!(transition_cause_for_status(&NodeStatus::Skipped), "SelectionFiltered");
    assert_eq!(transition_cause_for_status(&NodeStatus::Cached), "CachedReuse");
    assert_eq!(
        transition_cause_for_failure(Some(&FailureInfo {
            class: Some(bijux_dag_artifacts::FailureClass::Policy),
            kind: "Policy".to_string(),
            code: "POLICY_DENIED".to_string(),
            message: "policy denied".to_string(),
            details: None,
        })),
        "PolicyDenied"
    );
    assert_eq!(
        transition_cause_for_failure(Some(&FailureInfo {
            class: Some(bijux_dag_artifacts::FailureClass::Timeout),
            kind: "Execution".to_string(),
            code: "EXEC_TIMEOUT".to_string(),
            message: "timed out".to_string(),
            details: None,
        })),
        "TimeoutExceeded"
    );
    assert_eq!(
        transition_cause_for_failure(Some(&FailureInfo {
            class: Some(bijux_dag_artifacts::FailureClass::Infrastructure),
            kind: "Infrastructure".to_string(),
            code: "CONTAINER_ENGINE_UNAVAILABLE".to_string(),
            message: "missing engine".to_string(),
            details: None,
        })),
        "InfrastructureFailed"
    );
    assert_eq!(
        transition_cause_for_failure(Some(&FailureInfo {
            class: Some(bijux_dag_artifacts::FailureClass::User),
            kind: "Execution".to_string(),
            code: "OUTPUT_MISSING".to_string(),
            message: "missing output".to_string(),
            details: None,
        })),
        "MissingRequiredOutput"
    );
}

#[test]
fn skip_transition_causes_follow_recorded_skip_reason() {
    assert_eq!(transition_cause_for_skip_reason("filtered"), "SelectionFiltered");
    assert_eq!(
        transition_cause_for_skip_reason("not_selected_by_include_selector"),
        "SelectionFiltered"
    );
    assert_eq!(transition_cause_for_skip_reason("excluded_by_selector"), "SelectionFiltered");
    assert_eq!(transition_cause_for_skip_reason("upstream_failed"), "DependencyFailed");
    assert_eq!(transition_cause_for_skip_reason("cancelled"), "CancelRequested");
}