moon-statelens

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

    automata-learning
    mealy
    model-inference
    testing
    Download zip
    Version
    0.4.0
    License
    Apache-2.0
    Last updated
    7 days ago
    Downloads
    260

    #Tested embedding example

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

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

    Model-derived regression suites retain every original assertion when sharing prefixes. Independent fresh stability probes remain enabled by default:

    ///|
    test "saved assertions and successful-event safety rules" {
    let model = @lens.Machine::new(["logout", "request"], [
    [{ target: 0, output: "ok", }, { target: 0, output: "denied", }],
    ])
    let suite = model.regression_suite(profile="wp", state_bound=1)
    let imported = @lens.RegressionSuite::from_json(Json(suite))
    assert_true(imported.plan().validate())
    let report = imported
    .plan()
    .run(@lens.Experiment::new(model.system(), model.alphabet()))
    guard report is { "passed": True, .. } else { fail("regression") }
    let rule = @lens.BehaviorRule::new(
    "logged-out-denial",
    { input: "request", output: "allowed", },
    kind="after",
    trigger={ input: "logout", output: "ok", },
    )
    assert_true(rule.search(model).witness is None)
    assert_eq(rule.check_trace(["logout", "request"], ["ok", "allowed"]), Some(1))
    }

    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

    BehaviorRule

    pub struct BehaviorRule {
    // private fields
    }

    A two-state safety monitor. On one step: release, trigger, then prohibition. Thus a successful release cancels a prohibition on that same step unless the step also matches the trigger. These are finite safety rules, not full LTL.

    BehaviorRule::check_trace

    fn BehaviorRule::check_trace(self : BehaviorRule, inputs : Array[String], outputs : Array[String]) -> Int? raise LensError

    First violating output position in a complete actual trace, starting at reset.

    BehaviorRule::finding

    fn BehaviorRule::finding(self : BehaviorRule, search : RuleSearch, actual : Array[String]?) -> Json raise

    Assemble evidence from a complete reset-based replay; callers own its IO provenance.

    BehaviorRule::from_json

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

    BehaviorRule::new

    fn BehaviorRule::new(id : String, forbidden : RuleEvent, kind? : String, trigger? : RuleEvent, release? : RuleEvent) -> BehaviorRule raise LensError

    kind is forbidden, after, or until. after/until require trigger; until also requires release. after/until start inactive after reset.

    BehaviorRule::search

    fn BehaviorRule::search(self : BehaviorRule, model : Machine, max_pairs? : Int) -> RuleSearch raise LensError

    BFS over model × finite monitor. A witness is only a model prediction.

    BehaviorRule::to_json

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

    BehaviorRule::verify

    fn BehaviorRule::verify(self : BehaviorRule, model : Machine, experiment : Experiment, max_pairs? : Int) -> Json raise

    No model witness means no fresh target confirmation, even after full search.

    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, seed? : Machine, binary_cex? : Bool) -> 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::regression_suite

    fn Machine::regression_suite(self : Machine, profile? : String, state_bound? : Int, max_candidates? : Int, max_symbols? : Int, max_work? : Int) -> RegressionSuite raise

    Generate deterministic suites; truncation is explicit, never a proof.

    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.

    ModelDiagnosis

    pub(all) struct ModelDiagnosis {
    differences : Array[Difference]
    complete : Bool
    visited_pairs : Int
    checked_edges : Int
    reason : String?
    } derive(ToJson,
    Debug
    )

    Bounded differing edges in the reachable product graph. Contexts need not be independent defects. State numbers are never assumed to align.

    ModelDiagnosis::to_json

    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
    )

    RegressionCase

    pub(all) struct RegressionCase {
    inputs : Array[String]
    expected : Array[String]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    One independent reset-based experiment. Every output position is asserted.

    RegressionCase::equal

    RegressionCase::not_equal

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

    RegressionCase::to_json

    RegressionExecution

    pub struct RegressionExecution {
    // private fields
    }

    IO-independent execution cursor shared by synchronous and asynchronous runners.

    RegressionExecution::abort

    fn RegressionExecution::abort(self : RegressionExecution, reason : String, unstable? : Bool) -> Unit

    RegressionExecution::next_word

    fn RegressionExecution::next_word(self : RegressionExecution) -> Array[String]?

    Callers must execute every requested word with a fresh physical reset.

    RegressionExecution::observe

    fn RegressionExecution::observe(self : RegressionExecution, outputs : Array[String]) -> Unit raise

    RegressionExecution::report

    RegressionPlan

    pub struct RegressionPlan {
    // private fields
    }

    Every original case maps to a physical word whose output prefix asserts it. This changes reset scheduling under deterministic Mealy assumptions only.

    RegressionPlan::alphabet

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

    RegressionPlan::certificate

    fn RegressionPlan::certificate(self : RegressionPlan) -> Json

    RegressionPlan::execution

    fn RegressionPlan::execution(self : RegressionPlan, stability_rechecks? : Int) -> RegressionExecution raise

    RegressionPlan::run

    fn RegressionPlan::run(self : RegressionPlan, experiment : Experiment, stability_rechecks? : Int) -> Json raise

    Independent model mismatch is a regression, not proof of old/new real-target difference.

    RegressionPlan::validate

    fn RegressionPlan::validate(self : RegressionPlan) -> Bool raise

    Independently verify each prefix and every expected output position.

    RegressionPlan::words

    fn RegressionPlan::words(self : RegressionPlan) -> Array[Array[String]]

    RegressionSuite

    pub struct RegressionSuite {
    // private fields
    }

    Validated, owned suite. Only completed W/Wp generation carries a conditional state-bound claim; state/transition profiles provide model coverage only.

    RegressionSuite::cases

    RegressionSuite::coverage

    fn RegressionSuite::coverage(self : RegressionSuite) -> Json

    Coverage counts reachable model states/edges visited by the original suite.

    RegressionSuite::evidence

    fn RegressionSuite::evidence(self : RegressionSuite) -> Json

    RegressionSuite::from_json

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

    Recompute the generator and evidence instead of trusting imported claims.

    RegressionSuite::is_complete

    fn RegressionSuite::is_complete(self : RegressionSuite) -> Bool

    RegressionSuite::plan

    fn RegressionSuite::plan(self : RegressionSuite, compact? : Bool) -> RegressionPlan

    RegressionSuite::to_json

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

    RuleEvent

    pub(all) struct RuleEvent {
    input : String
    output : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    Rules match successful observable input/output events, not input names alone.

    RuleEvent::equal

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

    RuleEvent::not_equal

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

    RuleEvent::to_json

    fn RuleEvent::to_json(RuleEvent) -> Json

    RuleSearch

    pub(all) struct RuleSearch {
    witness : Array[String]?
    search_complete : Bool
    visited_pairs : Int
    reason : String?
    } derive(ToJson,
    Debug
    )

    RuleSearch::to_json

    fn RuleSearch::to_json(RuleSearch) -> Json

    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).

    diagnose_models

    fn diagnose_models(left : Machine, right : Machine, max_pairs? : Int, max_findings? : Int, max_edges? : Int) -> ModelDiagnosis raise LensError

    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.

    evidence_html

    fn evidence_html(report : Json) -> String

    Self-contained viewer. Target data lives in escaped inert text, never in a script literal or innerHTML. No network, CDN, tracking, or active target IO.

    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, seed? : Machine, binary_cex? : Bool) -> LearningResult raise

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

    regression_junit

    fn regression_junit(report : Json) -> String raise LensError

    CI consumes one testcase per original assertion plus an explicit failure when evidence is incomplete. Unknown formats are rejected.