Skip to content

EventFlow Static Analysis & Path Discovery Proposal

Static analysis for flow files: find unreachable states, discover paths, suggest tests.

Version: Draft 0.1 Date: December 2024 Status: Open Questions - Not Yet Decided


1. Executive Summary

EventFlow's --coverage flag shows what IS tested. Static analysis answers: what ISN'T tested, and what CAN'T be reached?

Core Philosophy

Coverage tells you what you did. Analysis tells you what you missed.

Key Features (Proposed)

  1. Unreachable State Detection - Find dead states with no incoming transitions
  2. Path Discovery - Enumerate all possible paths through the machine
  3. Test Gap Analysis - Identify untested paths
  4. Guard Conflict Detection - Find logically impossible guard combinations

2. Motivation

Current Situation

We have eventflow test --coverage which reports:

  • Which events have tests
  • Which guard branches are tested
  • Which states are entered

What's Missing

eventflow test order.flow --coverage

Coverage: 94% (17/18 branches)

But this doesn't answer:

  • Which branch is the missing 6%?
  • Are there states that can NEVER be reached?
  • What test should I write to cover the gap?

Real-World Need

flow
machine: @order

scenario: checkout

  on :checkout from @customer (api)
    ? cart is valid
      order moves to #processing
    ? cart is empty
      order moves to #error

  on :cancel from @customer
    order moves to #cancelled

  on :archive from @admin
    order moves to #archived    // Can this ever be reached?

Questions:

  • Is #archived reachable? (No on handler leads there except :archive)
  • Can :archive happen from any state? Or only certain states?
  • What's the happy path? #processing#shipped? Where's #shipped?

3. Proposed Solution: eventflow analyze

bash
eventflow analyze order.flow

Possible Output

Static Analysis: order.flow

States:
  #pending            ✓ reachable (initial)
  #processing         ✓ reachable
  #error              ✓ reachable
  #cancelled          ✓ reachable
  #archived           ⚠ reachable only via :archive (no guard path)
  #shipped            ✗ unreachable (defined but no transition leads here)

Paths Found: 5
  1. → #pending → :checkout → #processing
  2. → #pending → :checkout → #error
  3. → #pending → :cancel → #cancelled
  4. → #processing → :cancel → #cancelled
  5. → * → :archive → #archived

Untested Paths: 2
  - :checkout → #error (guard: cart is empty)
  - :archive → #archived

Suggested Tests:
  empty cart rejects checkout:
    with scenario:
      cart is empty
    = order is in #error

  admin can archive order:
    after :checkout
    receive :archive from @admin
    = order is in #archived

4. Open Questions (All Undecided)

Definitions

  • [ ] What is a "happy path"?

    • First event to a final state?
    • The path defined in the scenario's event sequence?
    • All paths that don't hit error states?
  • [ ] What is a "final state"?

    • State with no outgoing transitions?
    • Explicitly marked states?
    • States that match certain patterns (#completed, #done, #shipped)?

Analysis Scope

  • [ ] How should guards be analyzed?

    • Treat as opaque (any guard can be true or false)?
    • Attempt logical analysis (mutually exclusive guards)?
    • Use binding hints?
  • [ ] How should cross-machine paths work?

    • Analyze each machine independently?
    • Track event flow across machines?
    • Build combined state graph?
  • [ ] How should nested guards be evaluated?

    • Flatten to all possible combinations?
    • Preserve hierarchy in output?

Output Formats

  • [ ] CLI output format?

    • Structured text (as shown above)?
    • Tree format?
    • Table format?
  • [ ] JSON export needed?

    • For tooling integration?
    • For CI/CD pipelines?
  • [ ] HTML report needed?

    • Interactive exploration?
    • Shareable reports?
  • [ ] Diagram output (SVG/Mermaid)?

    • Visual state graph?
    • Highlight unreachable states?
    • Show tested vs untested paths?

CI/CD Integration

  • [ ] Should --strict flag fail on unreachable states?

    • Unreachable state = error?
    • Untested path = warning?
    • Configurable severity?
  • [ ] Warning vs Error distinction?

    • What's a warning? What's an error?
    • Should warnings block CI?

Test Suggestions

  • [ ] Should it suggest .test.flow snippets?

    • Generate ready-to-paste test code?
    • Just describe what to test?
  • [ ] What detail level for suggestions?

    • Minimal: "test :checkout → #error"
    • Full: complete test block with assertions

5. EventFlow Spirit

Whatever decisions are made, the analysis should follow EventFlow's principles:

Readable Output

Analysis output should read like documentation, not compiler errors:

# Good
States:
  #shipped    ✗ unreachable - no transition leads here

Suggestion: Add a transition to #shipped, or remove the state.

# Bad
ERROR: State 'shipped' has in-degree 0 in transition graph

Natural Language Suggestions

Test suggestions should be in EventFlow syntax:

flow
// Suggested test for untested path
admin archives pending order:
  given:
    order is in #pending
  receive :archive from @admin
  = order is in #archived

Visual When Helpful

If diagram output is added, it should enhance understanding:

#pending ──:checkout──▶ #processing ──:ship──▶ #shipped
    │                        │
    │                        ▼
    └────:cancel────▶ #cancelled

⚠ #archived has no incoming path from #pending

Released under the MIT License.