packages feed

haal-0.7.0.0: CHANGELOG.md

# Changelog for `haal`

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to the
[Haskell Package Versioning Policy](https://pvp.haskell.org/).

## 0.7.0.0 - 2026-10-10

This release replaces the built-in statistics with a general mechanism:
experiments report events, and statistics are folds over them. It also makes
`query` overridable, adds a way to compare automata, and removes the Moore and
DFA types. See "Migrating from 0.6" at the end of this entry.

### Added
- `query` is now a method of `Haal.BlackBox.SUL`, with the old behaviour
  (reset, then walk) as its default. A SUL can override it to answer a whole
  query at once, e.g. by sending the whole word in one message. The
  documentation of `query` lists what an override must guarantee.
- `Haal.BlackBox.queryChecked`: `query`, checking that the SUL returned exactly
  one output per input. The learners and oracles query through it, so an
  override that breaks this fails with a clear error instead of corrupting the
  observation table.
- `Haal.Statistics`, with the events an experiment reports (`Event`:
  `PhaseChanged`, `Queried`, `Hypothesis`, `Counterexample`, and `Phase`), the
  `Statistics` record and the `statistics` fold. `statistics` measures the
  membership queries and symbols sent while constructing hypotheses and while
  validating them, and records every hypothesis and counterexample. A
  statistic is a `Control.Foldl.Fold` over events, so statistics combine with
  `<*>` and users can define their own.
- `Haal.Experiment.experimentWith`: run an experiment, reporting every event
  to a monadic handler.
- `Haal.Experiment.measuredExperiment`: run an experiment and a fold over its
  events in one pass, returning the model and the fold's result.
- `Haal.BlackBox.difference`: a shortest input word on which two Mealy
  automata produce different outputs, or `Nothing` if they are equivalent.
- `MealyAutomaton` is a SUL in any monad, not only in `Identity`, so an
  automaton can be learned inside the monad that holds a user's own state.

### Changed (breaking)
- `Haal.Experiment.experiment` returns only the learned model, instead of the
  model and a `Statistics` record.
- `Haal.Experiment.Statistics` is removed; the new `Haal.Statistics.Statistics`
  record, measured with `measuredExperiment statistics`, replaces it. Its
  `hypotheses` include the final one; the number of rounds is
  `rounds stats`, and the number of equivalence queries is
  `length (hypotheses stats)`.
- `query` is a class method now, so a module that imports `SUL (..)` and
  defines its own top-level `query` gets an ambiguity error. Rename the local
  definition (the `io` example renames it to `askProgram`).
- New dependency: `foldl`.

### Removed
- `Haal.Automaton.MooreAutomaton` and `Haal.Automaton.DFA`. No learning
  algorithm in haal produces or uses Moore machines or DFAs, so supporting
  these types on their own was not useful, and their semantics had a known
  quirk (a step emitted the output of the state it left, not the one it
  reached). Support for more kinds of automata will come back properly in a
  future release, together with learning algorithms for them. To learn a DFA
  today, encode it as a Mealy machine with `Bool` outputs, where the output of
  a transition says whether the state it reaches is accepting.

### Migrating from 0.6

```haskell
-- 0.6
(model, stats) = runExperiment (experiment learner oracle) sul
-- statsRounds stats, statsCexs stats, statsHyps stats

-- 0.7: the model only
model = runExperiment (experiment learner oracle) sul

-- 0.7: the model and statistics
(model, stats) = runExperiment (measuredExperiment statistics learner oracle) sul
-- rounds stats, counterexamples stats, hypotheses stats (incl. the final one),
-- learning stats / testing stats: membership queries and symbols per phase

-- 0.7: your own statistics alongside, e.g. the number of events
import qualified Control.Foldl as L
(model, (stats, n)) = runExperiment (measuredExperiment ((,) <$> statistics <*> L.length) learner oracle) sul
```

## 0.6.1.1 - 2026-10-09

### Fixed
- The W-method and the Wp-method (and their random variants) did not test a
  hypothesis with a single state. Such a hypothesis has an empty characterizing
  set, so the W-method and the Wp-method generated no test words at all and
  accepted it without testing, at any depth, and the random Wp-method crashed
  with `Set.elemAt: index out of range`. This happens whenever no single input
  distinguishes the states of the SUL, so learning stopped after the first
  hypothesis with a wrong one-state model. Test words now end with the empty
  word when the characterizing set is empty, which still checks the outputs
  along the rest of the word.
- The second phase of the Wp-method started from the wrong prefixes: the state
  cover without the transition cover instead of the transition cover without
  the state cover. Depth `k` therefore only accounted for about `k - 1` extra
  states. Learned models can now be correct at a lower depth: on four
  `haal-models` protocol models, depth 1 now learns three of them correctly
  (before: none) and depth 2 all four (before: three).

## 0.6.1.0 - 2026-10-05

### Added
- `Haal.BlackBox.query` runs a single query: it resets the SUL, then walks it
  over the given inputs and returns the outputs.

### Fixed
- The SUL is now reset before every membership query and every equivalence
  test case. Before, queries only started from the initial state because the
  library kept returning to the same SUL value. That works for pure automata,
  but a SUL that wraps a stateful system (a running process, a socket) was
  queried from whatever state the previous query left it in, so learning could
  produce wrong models or never terminate. Learning from an automaton that is
  not in its initial state is fixed too.
- The documentation of `Haal.BlackBox.FiniteOrd` said `(Ord, Bounded)` instead
  of `(Ord, Finite)`.

### Changed
- Simplified the examples.

## 0.6.0.0 - 2026-10-03

### Added
- `Haal.Automaton.MealyAutomaton.mkMealyAutomatonTable` builds a Mealy
  automaton with states `0 .. n - 1` from a transition table and an output
  table, each encoded as a `String`, and validates both tables.
- `Haal.Dot.MealyTable` and `Haal.Dot.mealyTable` encode a `ParsedMealy` in
  that table form, rejecting automata with missing or conflicting
  transitions.

### Fixed
- `Haal.Dot.parseDot` again orders states, inputs, and outputs by first
  appearance, as documented. Since 0.5.0.0 it sorted them by name, which
  changed the constructor order and state numbering of generated modules.

### Changed (breaking)
- `Haal.Experiment.Learner` drops its state parameter (`Learner l aut`);
  learners now always produce automata with `Int` states.

### Changed
- `haal-gen` (`Haal.Dot.generateModule`) emits the transition and output
  functions as string-literal tables built with `mkMealyAutomatonTable`,
  instead of one equation per transition, and lists every transition in a
  comment. Generated modules have the same types and behaviour, but compile
  about 2-3 times faster. `generateModule` now rejects incomplete or
  nondeterministic automata, instead of generating functions that fail at
  runtime.
- `Haal.BlackBox.distinguish` returns `[]` immediately when both states are
  equal, instead of exploring the product automaton first. Results are
  unchanged. Its LiquidHaskell spec now states that equal states yield an
  empty word.

### Removed (breaking)
- `Haal.BlackBox.StateID` type alias. Learned automata use `Int` states
  directly, as `haal-gen` and `haal-models` already do; replace any use of
  `StateID` with `Int`.
- `Haal.Experiment.pairwiseWalk` and `Haal.Experiment.execute` are no longer
  exported; use `findCex` instead.

## 0.5.0.0 - 2026-04-22

### Fixed
- `Haal.Learning.LMstar.equivalenceClasses` now iterates over `Sm` only,
  matching Definition 2 of Shahbaz & Groz, "Inferring Mealy Machines".
  Previously it iterated over `Sm ∪ Sm·I`, which could pick a representative
  from `Sm·I` whose `rep++[i]` was not in the observation table, causing
  `makeHypothesis` to fail on many real protocol models (DTLS, medium MQTT)
  with `"invariant violation — makeHypothesis failed on closed consistent table"`.

### Changed (breaking)
- `Haal.Learning.LMstar.otIsConsistent` tightens its output constraint
  from `Eq o` to `Ord o` to support Map-based row grouping.

### Changed
- `Haal.Learning.LMstar.otIsConsistent` groups prefixes by row signature
  before pairwise comparison, reducing the worst-case pair enumeration.
- `Haal.Dot.parseDot` uses `Set.fromList` instead of `nub` for deduplication
  (O(n²) → O(n log n)).

### Added
- `tasty-bench`-based benchmark suite in `bench/` covering BlackBox
  operations, W-method / Wp-method test-suite generation, DOT
  serialize/parse/roundtrip, and end-to-end learning experiments.
  Run with `stack bench haal`.

## 0.4.1.0 - 2026-03-21

### Added 
- Serializer from Mealy Automaton to Dot format.
- haal-gen executable that accepts a .dot file and produces a haskell module
  that exports a function for the specified Mealy Automaton, with specific
  input and output types, rather than just using String for both.
- haal-models subpackage that exports learned models of tls, mqtt, tcp and dtls
  protocols.

## 0.4.0.2 - 2026-03-17

### Changed 
- Dependency bounds.

## 0.4.0.1 - 2026-03-17

### Added
- Optional `liquid` Cabal flag (`--flag haal:liquid`) to enable LiquidHaskell
  verification without requiring it as a dependency for normal builds.

### Verified
- `Haal.BlackBox`: `walk` produces outputs of length equal to the input length.
- `Haal.Learning.LMstar`: `ObservationTable` invariant that all entries in
  `mappingT` map to non-empty output lists, preserved across `updateMap`,
  `makeConsistent`, `makeClosed`, and `initializeOT`.

## 0.4.0.0 - 2026-03-16

### Changed 
- Changed the types of oracles' constructors from `<Oracle>` to `Either String <Oracle>`
  where `String` is an error message indicating invalid values to `<OracleConfig>`.
  Now, for example, instead of `oracle = mkWMethod (WMethodConfig 2)`, one should either 
  pattern match with `case` or do `oracle = either error id (mkWMethod (WMethodConfig 2))`.

## 0.3.0.0 - 2026-03-11

### Changed
- `SUL` typeclass no longer takes `i` and `o` as class parameters; they are now
  universally quantified in the method signatures. Instances should drop `i o`
  from their instance heads: `instance SUL MyType IO` instead of
  `instance SUL MyType IO Input Output`.
- `Automaton` typeclass likewise drops `i` and `o` from its class head.
  All constraint occurrences `(Automaton aut s i o)` become `(Automaton aut s)`.

## 0.2.0.0 - 2026-03-05

### Added
- `stepPure`, `walkPure`, `resetPure` exported from `Haal.BlackBox`
- `Config` record types for all equivalence oracles: `WMethodConfig`, `WpMethodConfig`,
  `RandomWalkConfig`, `RandomWordsConfig`, `RandomWMethodConfig`, `RandomWpMethodConfig`
- `mkCombinedOracle` smart constructor for `CombinedOracle`
- `randomWordsConfig` accessor for `RandomWords`
- `mealyDelta`, `mealyLambda` as explicit named exports from `Haal.Automaton.MealyAutomaton`

### Changed
- All oracle constructors now take a `Config` record instead of positional arguments:
  `mkWMethod :: WMethodConfig -> WMethod`, `mkWpMethod :: WpMethodConfig -> WpMethod`, etc.
- `mkRandomWMethod` and `mkRandomWpMethod` now take a `Config` record instead of
  positional arguments (also fixes an argument-order bug in the old interface)

### Removed
- `mealyStep` from `Haal.Automaton.MealyAutomaton`; use `stepPure` from `Haal.BlackBox`
- `mooreStep` from `Haal.Automaton.MooreAutomaton`; use `stepPure` from `Haal.BlackBox`
- Raw constructor exports (`MealyAutomaton (..)`, `WMethod (..)`, `WpMethod (..)`,
  `RandomWalk (..)`, `RandomWords (..)`, `CombinedOracle (..)`, `LMstar (..)`);
  use the corresponding `mk`-prefixed smart constructors instead

## 0.1.0.0 - 2025-12-02

- Initial release of `haal`.
    - Support for Mealy Automata and DFAs.
    - One learner for Mealy Automata and DFAs with 2 configurations. 
        - LStar.
        - LPlus.
    - Basic equivalence oracles. 
    - Examples that showcase usage of the library.