hara-native 0.1.14

HAL-free native host runtime and package launcher for Hara
Documentation
use super::{
    InterpreterLiveSession, LiveBackend, LiveReplacementPolicy, LiveSession, LiveSessionCommand,
    LiveSessionOperation, LiveSessionRequest, LiveSessionState, LiveSessionStatus, LiveSource,
    LIVE_SESSION_PROTOCOL, LIVE_SESSION_STATE_SCHEMA,
};

fn source(id: &str, revision: &str, text: &str) -> LiveSource {
    LiveSource::new(id, revision, text).unwrap()
}

fn request(
    id: &str,
    session: &impl LiveSession,
    command: LiveSessionCommand,
) -> LiveSessionRequest {
    LiveSessionRequest::for_state(id, &session.state(), command)
}

fn dispatch_ok(
    session: &mut impl LiveSession,
    id: &str,
    command: LiveSessionCommand,
) -> super::LiveSessionReply {
    let request = LiveSessionRequest::for_state(id, &session.state(), command);
    session.dispatch(request).unwrap()
}

#[test]
fn state_and_capabilities_publish_one_backend_neutral_contract() {
    let state = LiveSessionState {
        session_id: "fixture/live-model".into(),
        source_id: "model.hal".into(),
        generation: 3,
        revision: "sha256:model".into(),
        sequence: 17,
        backend: LiveBackend::Interpreter,
        status: LiveSessionStatus::Suspended,
    };
    let encoded = state.to_json();
    assert_eq!(encoded["schema"], LIVE_SESSION_STATE_SCHEMA);
    assert_eq!(encoded["protocol"], LIVE_SESSION_PROTOCOL);
    assert_eq!(encoded["session-id"], "fixture/live-model");
    assert_eq!(encoded["backend"], "interpreter");
    assert_eq!(encoded["status"], "suspended");

    let mut session = InterpreterLiveSession::start(
        "fixture/live-capabilities",
        source("capabilities.hal", "sha256:capabilities", "(+ 1 2)"),
    )
    .unwrap();
    let capabilities = session.capabilities();
    assert!(capabilities.supports(LiveSessionOperation::Update));
    assert!(!capabilities.supports(LiveSessionOperation::Pause));
    assert_eq!(capabilities.to_json()["protocol"], LIVE_SESSION_PROTOCOL);
    dispatch_ok(
        &mut session,
        "dispose-capabilities",
        LiveSessionCommand::Dispose,
    );
}

#[test]
fn interpreter_adapter_normalizes_lifecycle_and_source_replacement() {
    let mut session = InterpreterLiveSession::start(
        "fixture/live-interpreter",
        source("first.hal", "sha256:first", "(+ 1 (* 2 3))"),
    )
    .unwrap();
    assert_eq!(session.state().status, LiveSessionStatus::Ready);

    let run = dispatch_ok(
        &mut session,
        "run-first",
        LiveSessionCommand::Run {
            boundary_limit: 1_000,
        },
    );
    assert_eq!(run.state.status, LiveSessionStatus::Returned);
    assert_eq!(run.payload["status"], "returned");

    let reset = dispatch_ok(&mut session, "reset-first", LiveSessionCommand::Reset);
    assert_eq!(reset.state.generation, 1);
    assert_eq!(reset.state.status, LiveSessionStatus::Ready);

    let queued = dispatch_ok(
        &mut session,
        "queue-second",
        LiveSessionCommand::Update {
            source: source("second.hal", "sha256:second", "(+ 40 2)"),
            policy: LiveReplacementPolicy::ReplaceOnNextStart,
        },
    );
    assert_eq!(queued.state.revision, "sha256:first");
    assert_eq!(session.pending_revision(), Some("sha256:second"));

    let activated = dispatch_ok(&mut session, "activate-second", LiveSessionCommand::Reset);
    assert_eq!(activated.state.generation, 2);
    assert_eq!(activated.state.revision, "sha256:second");
    assert_eq!(activated.state.source_id, "second.hal");

    let rerun = dispatch_ok(
        &mut session,
        "run-second",
        LiveSessionCommand::Run {
            boundary_limit: 1_000,
        },
    );
    assert_eq!(rerun.state.status, LiveSessionStatus::Returned);

    dispatch_ok(
        &mut session,
        "dispose-interpreter",
        LiveSessionCommand::Dispose,
    );
    assert_eq!(session.state().status, LiveSessionStatus::Disposed);
}

#[test]
fn stale_generation_is_rejected_before_backend_mutation() {
    let mut session = InterpreterLiveSession::start(
        "fixture/live-stale",
        source("stale.hal", "sha256:stale", "(+ 1 2)"),
    )
    .unwrap();
    let before = session.state();
    let mut stale = request("stale-step", &session, LiveSessionCommand::Step);
    stale.generation = Some(before.generation + 1);
    let error = session.dispatch(stale).unwrap_err();
    assert_eq!(error.code(), "live-session/stale-generation");
    assert_eq!(session.state(), before);

    dispatch_ok(&mut session, "dispose-stale", LiveSessionCommand::Dispose);
}

#[cfg(feature = "bytecode-observation")]
#[test]
fn hbc_adapter_uses_the_same_identity_and_lifecycle_contract() {
    use super::BytecodeLiveSession;

    let mut session = BytecodeLiveSession::compile(
        "fixture/live-hbc",
        source("bytecode.hal", "sha256:hbc-first", "(+ 19 23)"),
    )
    .unwrap();
    assert_eq!(session.state().backend, LiveBackend::Hbc);
    assert!(session.capabilities().supports(LiveSessionOperation::Pause));

    let run = dispatch_ok(
        &mut session,
        "run-hbc",
        LiveSessionCommand::Run {
            boundary_limit: 1_000,
        },
    );
    assert_eq!(run.state.status, LiveSessionStatus::Returned);

    dispatch_ok(&mut session, "reset-hbc", LiveSessionCommand::Reset);
    let paused = dispatch_ok(&mut session, "pause-hbc", LiveSessionCommand::Pause);
    assert_eq!(paused.state.status, LiveSessionStatus::Paused);
    let resumed = dispatch_ok(
        &mut session,
        "resume-hbc",
        LiveSessionCommand::Resume { settlement: None },
    );
    assert_eq!(resumed.state.status, LiveSessionStatus::Ready);

    let replaced = dispatch_ok(
        &mut session,
        "replace-hbc",
        LiveSessionCommand::Update {
            source: source("replacement.hal", "sha256:hbc-second", "(+ 40 2)"),
            policy: LiveReplacementPolicy::Restart,
        },
    );
    assert_eq!(replaced.state.generation, 2);
    assert_eq!(replaced.state.revision, "sha256:hbc-second");

    let cancelled = dispatch_ok(&mut session, "cancel-hbc", LiveSessionCommand::Cancel);
    assert_eq!(cancelled.state.status, LiveSessionStatus::Cancelled);
    let blocked_request = request("step-cancelled-hbc", &session, LiveSessionCommand::Step);
    let blocked = session.dispatch(blocked_request).unwrap_err();
    assert_eq!(blocked.code(), "live-session/cancelled");

    dispatch_ok(&mut session, "dispose-hbc", LiveSessionCommand::Dispose);
}

#[cfg(all(feature = "bytecode-observation", feature = "bytecode-instrumentation"))]
#[test]
fn instrumented_hbc_live_session_starts_from_validated_artifact() {
    use super::instrumented_hbc::InstrumentedHbcLiveSession;
    use crate::vm::{compile_source, encode_program};
    use crate::Runtime;

    let program = compile_source("(+ 19 23)").unwrap();
    let artifact = encode_program(&program).unwrap();
    let runtime = Runtime::core();
    let mut session = InstrumentedHbcLiveSession::start_from_artifact(
        &runtime,
        "fixture/live-artifact-owner",
        "fixture/live-artifact",
        source("artifact.hal", "sha256:artifact", "(+ 19 23)"),
        &artifact,
    )
    .unwrap();

    let run = dispatch_ok(
        &mut session,
        "run-artifact",
        LiveSessionCommand::Run {
            boundary_limit: 1_000,
        },
    );
    assert_eq!(run.state.status, LiveSessionStatus::Returned);
    assert_eq!(run.payload["result"], 42);

    dispatch_ok(
        &mut session,
        "dispose-artifact",
        LiveSessionCommand::Dispose,
    );
}

#[cfg(all(feature = "whole-wasm", not(target_arch = "wasm32")))]
#[test]
fn whole_wasm_live_session_exposes_prepared_call_contract_only() {
    use super::whole_wasm::WholeWasmLiveSession;
    let runtime = crate::Runtime::core();

    let mut session = WholeWasmLiveSession::start(
        &runtime,
        "fixture/session",
        "fixture/live-whole-wasm",
        source("whole-wasm.hal", "sha256:whole-wasm", "(+ 19 23)"),
    )
    .unwrap();
    let capabilities = session.capabilities();
    assert!(capabilities.supports(LiveSessionOperation::Run));
    assert!(capabilities.supports(LiveSessionOperation::Call));
    assert!(!capabilities.supports(LiveSessionOperation::Step));
    assert!(!capabilities.supports(LiveSessionOperation::Snapshot));

    let run = dispatch_ok(
        &mut session,
        "run-whole-wasm",
        LiveSessionCommand::Run {
            boundary_limit: 1_000,
        },
    );
    assert_eq!(run.state.status, LiveSessionStatus::Returned);
    assert_eq!(run.payload["result"], 42);

    dispatch_ok(
        &mut session,
        "dispose-whole-wasm",
        LiveSessionCommand::Dispose,
    );
}