Sign in

    moon-statelens

    Active black-box Mealy model learning and replay-confirmed behavioral compatibility checks.

    automata-learning
    mealy
    model-inference
    testing
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    3 days ago
    Downloads
    67

    #Tested embedding example

    Add CaptainK-65/moon-statelens@0.1.0 to your module and this import to its moon.pkg:

    ///|
    import {
    "CaptainK-65/moon-statelens" @lens,
    }

    The following blocks are executed by moon test. The learner sees only the adapter, not the hidden state or a supplied reference model. Its conclusion is conditional on the declared two-state bound and resettable deterministic Mealy assumptions.

    ///|
    test "quick start learns a resettable session" {
    let active = [false]
    let target = @lens.System::new(reset=() => active[0] = false, step=input => {
    match input {
    "login" => {
    active[0] = true
    "ok"
    }
    "logout" => {
    active[0] = false
    "ok"
    }
    "request" => if active[0] { "allowed" } else { "denied" }
    _ => raise @lens.UnknownInput(input)
    }
    })
    let experiment = @lens.Experiment::new(target, ["login", "logout", "request"])
    let result = @lens.learn(experiment, @lens.Oracle::w_method(2))
    assert_true(result.status is Supported(ConditionalStateBound(2)))
    let model = result.model.unwrap()
    assert_eq(model.state_count(), 2)
    assert_eq(model.trace(["login", "logout", "request"]), ["ok", "ok", "denied"])
    assert_true(
    @lens.distinguish(model, @lens.decode_model(model.encode())) is None,
    )
    }

    Budget exhaustion is data, not equivalence:

    ///|
    test "quick start preserves an inconclusive query budget" {
    let target = @lens.System::new(reset=() => (), step=_ => "ok")
    let experiment = @lens.Experiment::new(target, ["x"], max_queries=0)
    let result = @lens.learn(experiment, @lens.Oracle::w_method(1))
    assert_true(result.status is Inconclusive("physical-queries"))
    assert_true(result.model is None)
    }

    LensError

    pub(all) suberror LensError {
    InvalidArgument(String)
    UnknownInput(String)
    LimitExceeded(String)
    InconsistentObservation(word~ : Array[String], expected~ : Array[String], actual~ : Array[String])
    } derive(
    Debug
    )

    Invalid inputs, inconsistent observations and explicit resource limits.

    Algorithm

    pub(all) enum Algorithm {
    LStar
    KearnsVazirani
    } derive(Eq,
    Debug
    )

    Structurally different Mealy learners sharing query and model infrastructure.

    Algorithm::equal

    fn Algorithm::equal(Algorithm, Algorithm) -> Bool

    Algorithm::name

    fn Algorithm::name(self : Algorithm) -> String

    Algorithm::not_equal

    fn Algorithm::not_equal(x : Algorithm, y : Algorithm) -> Bool

    CheckResult

    pub(all) enum CheckResult {
    Counterexample(Array[String])
    NoCounterexample(Evidence)
    Inconclusive(String)
    } derive(
    Debug
    )

    Custom oracle contract: returned counterexamples must actually distinguish the supplied hypothesis from the experiment's target.

    CompatibilityResult

    pub(all) struct CompatibilityResult {
    status : CompatibilityStatus
    left : LearningResult
    right : LearningResult
    left_statistics : Statistics
    right_statistics : Statistics
    refinements : Int
    spurious_inputs : Array[Array[String]]
    } derive(
    Debug
    )

    CompatibilityResult::to_json

    CompatibilityStatus

    pub(all) enum CompatibilityStatus {
    ConfirmedDifference(Difference)
    NoDifferenceBetweenHypotheses
    Inconclusive(String)
    ReplayUnstable(side~ : String, word~ : Array[String], expected~ : Array[String], actual~ : Array[String])
    } derive(
    Debug
    )

    CompatibilityStatus::to_json

    Difference

    pub(all) struct Difference {
    inputs : Array[String]
    left_outputs : Array[String]
    right_outputs : Array[String]
    } derive(Eq, ToJson,
    Debug
    )

    A replayable difference between two complete models.

    Difference::equal

    fn Difference::equal(Difference, Difference) -> Bool

    Difference::not_equal

    fn Difference::not_equal(x : Difference, y : Difference) -> Bool

    Difference::to_json

    fn Difference::to_json(Difference) -> Json

    DriverMetrics

    pub(all) struct DriverMetrics {
    states : Int
    access_sequences : Int
    discriminators : Int
    table_cells : Int
    table_symbols : Int
    tree_nodes : Int
    } derive(ToJson,
    Debug
    )

    DriverMetrics::to_json

    Evidence

    pub(all) enum Evidence {
    ExactReference
    ConditionalStateBound(Int)
    Budgeted(String)
    } derive(
    Debug
    )

    Scope of the experiment that supported a hypothesis. ExactReference refers to a supplied explicit reference model; it does not certify an arbitrary adapter.
    impl ToJson for Evidence

    Evidence::to_json

    fn Evidence::to_json(self : Evidence) -> Json

    Evidence::to_repr

    Experiment

    pub struct Experiment {
    // private fields
    }

    Synchronous compatibility facade over the IO-independent query session.

    Experiment::alphabet

    fn Experiment::alphabet(self : Experiment) -> Array[String]

    Experiment::new

    fn Experiment::new(system : System, alphabet : Array[String], max_queries? : Int, max_steps? : Int, max_word_length? : Int, max_cache_nodes? : Int, max_output_code_units? : Int, use_cache? : Bool, recheck_every? : Int) -> Experiment raise LensError

    Experiment::query

    fn Experiment::query(self : Experiment, requested_word : Array[String], bypass_cache? : Bool) -> Array[String] raise

    Experiment::statistics

    fn Experiment::statistics(self : Experiment) -> Statistics

    LearningDriver

    pub struct LearningDriver {
    // private fields
    }

    IO-independent suspended learner. Execute Query using a QuerySession/runner; verify each Hypothesis, then refine with a freshly executed counterexample.

    LearningDriver::algorithm

    fn LearningDriver::algorithm(self : LearningDriver) -> Algorithm

    LearningDriver::event

    fn LearningDriver::event(self : LearningDriver, queries? : Int) -> LearningEvent raise LensError

    Repeated calls return the same outstanding event with defensive word copies.

    LearningDriver::metrics

    LearningDriver::new

    fn LearningDriver::new(algorithm : Algorithm, alphabet : Array[String], max_states? : Int, max_rounds? : Int, max_table_cells? : Int, max_table_symbols? : Int, max_prefixes? : Int) -> LearningDriver raise

    LearningDriver::refine

    fn LearningDriver::refine(self : LearningDriver, requested : Array[String], actual : Array[String]) -> Unit raise

    Validate and trim a real counterexample at its first mismatching output.

    LearningDriver::result

    fn LearningDriver::result(self : LearningDriver, statistics : Statistics, status : LearningStatus) -> LearningResult

    Snapshot the last COMPLETE hypothesis, never an in-progress transition table.

    LearningDriver::submit

    fn LearningDriver::submit(self : LearningDriver, id : Int, outputs : Array[String]) -> Unit raise

    LearningEvent

    pub(all) enum LearningEvent {
    Query(QueryRequest)
    Hypothesis(Machine)
    } derive(
    Debug
    )

    LearningResult

    pub(all) struct LearningResult {
    model : Machine?
    status : LearningStatus
    statistics : Statistics
    rounds : Array[LearningRound]
    counterexamples : Array[Difference]
    } derive(
    Debug
    )

    LearningResult::to_json

    fn LearningResult::to_json(self : LearningResult) -> Json

    LearningRound

    pub(all) struct LearningRound {
    index : Int
    states : Int
    prefixes : Int
    suffixes : Int
    table_cells : Int
    table_symbols : Int
    queries : Int
    counterexample_length : Int?
    } derive(ToJson,
    Debug
    )

    LearningRound::to_json

    LearningStatus

    pub(all) enum LearningStatus {
    Supported(Evidence)
    Inconclusive(String)
    Unstable(word~ : Array[String], expected~ : Array[String], actual~ : Array[String])
    } derive(
    Debug
    )

    LearningStatus::to_json

    fn LearningStatus::to_json(self : LearningStatus) -> Json

    Machine

    pub struct Machine {
    // private fields
    } derive(
    Debug
    )

    Validated model; construction and accessors defensively copy arrays.
    impl ToJson for Machine

    Machine::access_sequences

    fn Machine::access_sequences(self : Machine) -> Array[(Int, Array[String])]

    Shortest access words for reachable states, in BFS/input order.

    Machine::alphabet

    fn Machine::alphabet(self : Machine) -> Array[String]

    Machine::characterizing_set

    fn Machine::characterizing_set(self : Machine, max_work? : Int) -> Array[Array[String]] raise LensError

    Pairwise shortest distinguishing suffixes for a reachable minimal model. Includes epsilon so single-state models test outputs. Preparation and witness reconstruction share a work budget; exhaustion never returns a partial set.

    Machine::distinguish_states

    fn Machine::distinguish_states(self : Machine, left : Int, right : Int) -> Array[String]? raise LensError

    Distinguish two states of one model. Equivalent states return None.

    Machine::encode

    fn Machine::encode(self : Machine) -> String

    Encode a model in the versioned interchange format.

    Machine::from_json

    fn Machine::from_json(value : Json) -> Machine raise

    Decode a versioned complete model, then apply all constructor invariants.

    Machine::initial_state

    fn Machine::initial_state(self : Machine) -> Int

    Machine::minimize

    fn Machine::minimize(self : Machine, max_work? : Int) -> Machine raise LensError

    Remove unreachable states and merge observationally equivalent states. The result uses BFS numbering starting at state zero.

    Machine::new

    fn Machine::new(alphabet : Array[String], transitions : Array[Array[Transition]], initial? : Int) -> Machine raise LensError

    Construct a complete machine. Table columns follow alphabet order.

    Machine::state_count

    fn Machine::state_count(self : Machine) -> Int

    Machine::system

    fn Machine::system(self : Machine) -> System

    Treat this explicit model as a black box. The learner receives only callbacks.

    Machine::table

    fn Machine::table(self : Machine) -> Array[Array[Transition]]

    Machine::to_dot

    fn Machine::to_dot(self : Machine) -> String

    DOT uses quoted JSON string escapes for textual labels, so quotes, backslashes and line breaks cannot escape into graph statements.

    Machine::to_json

    fn Machine::to_json(self : Machine) -> Json

    Machine::to_repr

    Machine::trace

    fn Machine::trace(self : Machine, word : Array[String]) -> Array[String] raise LensError

    Execute an input word starting at the model's initial state.

    Machine::trace_from

    fn Machine::trace_from(self : Machine, state : Int, word : Array[String]) -> Array[String] raise LensError

    Execute an input word starting at an explicitly selected state.

    Oracle

    pub struct Oracle {
    // private fields
    }

    Oracle::evaluate

    fn Oracle::evaluate(self : Oracle, hypothesis : Machine, experiment : Experiment) -> CheckResult raise

    Run a conformance oracle directly. Unlike learn, this propagates resource limits to the caller. Hypothesis and experiment alphabets must agree.

    Oracle::exact

    fn Oracle::exact(reference : Machine, max_pairs? : Int) -> Oracle raise LensError

    Exact equivalence to a supplied explicit model. Intended for fixtures and independent algorithm tests. The caller must establish that the adapter implements that reference; this oracle cannot establish it for an unknown SUL.

    Oracle::exhaustive

    fn Oracle::exhaustive(max_depth : Int, max_tests? : Int) -> Oracle raise LensError

    Fresh exhaustive experiments through max_depth. Completion is bounded evidence only. max_tests or an experiment limit yields Inconclusive.

    Oracle::new

    fn Oracle::new(check : (Machine, Experiment) -> CheckResult raise) -> Oracle

    Oracle::plan

    fn Oracle::plan(plan : VerificationPlan) -> Oracle raise LensError

    A synchronous facade over the same suspendable verification used by async IO.

    Oracle::random_words

    fn Oracle::random_words(seed? : UInt, max_length? : Int, tests? : Int) -> Oracle raise LensError

    Reproducible random-word experiments. Successful completion is never an unconditional equivalence result. The seed is reset for each hypothesis.

    Oracle::w_method

    fn Oracle::w_method(state_bound : Int, max_tests? : Int, max_characterization_work? : Int) -> Oracle raise LensError

    Complete W-method suite P . Sigma^{<=m-n} . W, where P is a transition cover and W characterizes a minimized, reachable hypothesis. Completeness is conditional on the actual SUL satisfying the declared state bound and deterministic, total, resettable Mealy assumptions. Truncation is inconclusive.

    Oracle::with_regressions

    fn Oracle::with_regressions(self : Oracle, words : Array[Array[String]]) -> Oracle

    Replay supplied regression words before delegating to this oracle. Words are copied; observations bypass the cache and can refine a stale hypothesis.

    Oracle::wp_method

    fn Oracle::wp_method(state_bound : Int, max_tests? : Int, max_characterization_work? : Int) -> Oracle raise LensError

    QueryRequest

    pub(all) struct QueryRequest {
    id : Int
    inputs : Array[String]
    } derive(
    Debug
    )

    A requested physical query. The runner must reset, execute each input, then clean up successfully before committing. Arrays are defensive copies.

    QuerySession

    pub struct QuerySession {
    // private fields
    }

    IO-independent cache, budgets and observation consistency. Exactly one outstanding query is allowed. Failed/cancelled work must be aborted.

    QuerySession::abort

    fn QuerySession::abort(self : QuerySession, id : Int) -> Unit

    Discard an unfinished query. Idempotent after commit/abort for safe defers. A different outstanding request cannot be aborted by an old identifier.

    QuerySession::accept_output

    fn QuerySession::accept_output(self : QuerySession, id : Int, output : String) -> Unit raise LensError

    QuerySession::alphabet

    fn QuerySession::alphabet(self : QuerySession) -> Array[String]

    QuerySession::commit

    fn QuerySession::commit(self : QuerySession, id : Int) -> Array[String] raise LensError

    Commit only AFTER successful target cleanup. Conflicts poison the session.

    QuerySession::new

    fn QuerySession::new(alphabet : Array[String], max_queries? : Int, max_steps? : Int, max_word_length? : Int, max_cache_nodes? : Int, max_output_code_units? : Int, use_cache? : Bool, recheck_every? : Int) -> QuerySession raise LensError

    QuerySession::reset_started

    fn QuerySession::reset_started(self : QuerySession, id : Int) -> Unit raise LensError

    Record the attempted reset immediately before invoking the target.

    QuerySession::start

    fn QuerySession::start(self : QuerySession, requested : Array[String], bypass_cache? : Bool) -> QueryStart raise LensError

    QuerySession::statistics

    fn QuerySession::statistics(self : QuerySession) -> Statistics

    QuerySession::step_started

    fn QuerySession::step_started(self : QuerySession, id : Int) -> Unit raise LensError

    Record an attempted step before invoking it, including attempts that fail.

    QueryStart

    pub(all) enum QueryStart {
    Cached(Array[String])
    Execute(QueryRequest)
    } derive(
    Debug
    )

    Statistics

    pub(all) struct Statistics {
    queries : Int
    cache_hits : Int
    resets : Int
    steps : Int
    completed_queries : Int
    cache_nodes : Int
    output_code_units : Int
    } derive(ToJson,
    Debug
    )

    Immutable counters. Attempts include target calls that fail.

    Statistics::to_json

    fn Statistics::to_json(Statistics) -> Json

    System

    pub struct System {
    // private fields
    }

    Reset/step adapter. Callbacks may raise; close is attempted on every physical query.

    System::new

    fn System::new(reset~ : () -> Unit raise, step~ : (String) -> String raise, close? : () -> Unit raise) -> System

    Transition

    pub(all) struct Transition {
    target : Int
    output : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    One transition of a complete deterministic Mealy machine.

    Transition::equal

    fn Transition::equal(Transition, Transition) -> Bool

    Transition::not_equal

    fn Transition::not_equal(x : Transition, y : Transition) -> Bool

    Transition::to_json

    fn Transition::to_json(Transition) -> Json

    VerificationCursor

    pub struct VerificationCursor {
    // private fields
    }

    VerificationCursor::event

    VerificationCursor::new

    fn VerificationCursor::new(plan : VerificationPlan, hypothesis : Machine) -> VerificationCursor raise

    VerificationCursor::stages

    VerificationCursor::submit

    fn VerificationCursor::submit(self : VerificationCursor, id : Int, outputs : Array[String]) -> Unit raise

    VerificationEvent

    pub(all) enum VerificationEvent {
    Query(QueryRequest)
    Completed(CheckResult)
    } derive(
    Debug
    )

    VerificationPlan

    pub(all) enum VerificationPlan {
    Exact(Machine)
    Exhaustive(depth~ : Int, max_tests~ : Int)
    RandomWords(seed~ : UInt, max_length~ : Int, tests~ : Int)
    WMethod(bound~ : Int, max_tests~ : Int, max_work~ : Int)
    WpMethod(bound~ : Int, max_tests~ : Int, max_work~ : Int)
    Replay(Array[Array[String]])
    Chain(Array[VerificationPlan])
    } derive(
    Debug
    )

    Declarative, suspendable conformance strategies. State-bound methods require deterministic, total, reliably resettable targets and a CORRECT state bound.

    VerificationStage

    pub(all) struct VerificationStage {
    strategy : String
    tests : Int
    result : String
    } derive(ToJson,
    Debug
    )

    VerificationStage::to_json

    compare

    fn compare(left : Experiment, right : Experiment, left_oracle : Oracle, right_oracle : Oracle, max_states? : Int, max_refinements? : Int) -> CompatibilityResult raise

    Infer both behaviors and replay model differences on the real adapters. Spurious hypothesis disagreements refine later experiments. Equal hypotheses remain qualified by each learning result's evidence. Final statistics include the real confirmation calls, even when those calls exhaust a budget.

    confirm_difference

    fn confirm_difference(left : Experiment, right : Experiment, inputs : Array[String]) -> Difference? raise

    Freshly replay a candidate word against both real targets.

    decode_model

    fn decode_model(text : String, max_code_units? : Int) -> Machine raise

    Parse a bounded JSON document (limit counts UTF-16 code units, not bytes).

    distinguish

    fn distinguish(left : Machine, right : Machine, max_pairs? : Int) -> Difference? raise LensError

    BFS in the reachable product graph. None is exact observable equivalence of the two supplied complete models, not a theorem about unknown programs.

    learn

    fn learn(experiment : Experiment, oracle : Oracle, max_states? : Int, max_rounds? : Int, max_table_cells? : Int, max_table_symbols? : Int, max_prefixes? : Int) -> LearningResult raise

    Synchronous L* compatibility entry. The same suspended learner also powers native asynchronous runners.

    learn_with

    fn learn_with(experiment : Experiment, oracle : Oracle, algorithm : Algorithm, max_states? : Int, max_rounds? : Int, max_table_cells? : Int, max_table_symbols? : Int, max_prefixes? : Int) -> LearningResult raise

    Select L* or KV without changing the synchronous custom-oracle contract.