TLX Roadmap

Copy Markdown

Vision

Enable Elixir developers to write formally verifiable TLA+/PlusCal specifications using a Spark DSL, bridging the gap between Elixir's actor model and TLA+'s formal verification.

Phases

Phase 1: Foundation (complete)

Target: v0.1.x Focus: Core DSL, internal IR, TLA+ emitter

  • [x] Spark DSL for variables, constants, init, actions (guard + next)
  • [x] Internal IR structs (%TLX.Spec{}, %TLX.Action{}, etc.)
  • [x] TLA+ emitter (generate valid .tla files from IR)
  • [x] Invariant declarations
  • [x] First example spec: simple state machine
  • [x] Foundational ADRs (0001, 0002, 0003)

Phase 2: PlusCal and Concurrency (complete)

Target: v0.2.x Focus: Process support, PlusCal emission, temporal properties

  • [x] Process declarations (concurrent actors)
  • [x] PlusCal emitter (labels, await, either/or, with)
  • [x] Temporal properties (always, eventually)
  • [x] Fairness annotations (weak/strong)
  • [x] Quantifiers (exists, forall)
  • [x] Non-deterministic choice
  • [x] TLC integration (invoke TLC as subprocess, parse results)

Phase 3: Simulation and Tooling (complete)

Target: v0.3.x Focus: Elixir simulator, Mix tasks, developer experience

  • [x] Elixir simulator (random walk state exploration)
  • [x] mix tlx.check task (emit + run TLC)
  • [x] mix tlx.simulate task (Elixir random exploration)
  • [x] Counterexample trace formatting
  • [x] Spark introspection and documentation generation

Phase 4: Production Readiness (complete)

Target: v1.0.0 Focus: Hardening, documentation, public release

  • [x] Comprehensive test suite
  • [x] Diátaxis documentation (tutorials, how-to, reference, explanation)
  • [x] Hex.pm publication (package metadata ready, publish when public)
  • [x] Real-world example specs (mutex, producer-consumer)

Phase 5: Semantic Intelligence (proposed)

Target: v0.2.x Focus: Compile-time analysis, smarter diagnostics

  • [x] Auto-generated TypeOK invariants from variable usage
  • [x] Empty action / zero-transition warnings
  • [x] Better error messages with source locations and suggestions
  • [x] Simulator constant injection

Phase 6: Expressiveness (complete)

Target: v0.2.x Focus: Close the gap with full TLA+/PlusCal

  • [x] IF/THEN/ELSE in expressions (ite/3)
  • [x] Set operations (union, intersect, subset, cardinality, set_of, in_set)
  • [x] Non-deterministic set pick (pick :var, :set)
  • [x] Custom Init expressions (initial section with constraint)
  • [x] LET/IN local definitions (let_in/3)

Phase 7: Tooling (complete)

Target: v0.3.x Focus: Developer workflow

  • [x] mix tlx.watch — auto-simulate on save
  • [x] mix tlx.list — discover spec modules
  • [x] Extract shared AST formatting into TLX.Emitter.Format (symbol-table-parameterized)
  • [x] CI integration (GitHub Actions workflow)
  • [x] Mix task naming: Mix.Tasks.Tlx.* (Mix convention)

Phase 8: Forge Integration (complete)

Target: v0.3.x Focus: Bridge to the original motivation

  • [x] GenStateMachine → TLX skeleton generator
  • [x] TLA+ → TLX importer
  • [x] Forge example specs — 6 abstract (from ADRs) + 6 concrete (from code), all TLC-verified
  • [x] Refinement checking — concrete specs refine abstract specs via INSTANCE/WITH

Phase 9: Robustness (complete)

Target: v0.2.x Focus: Replace fragile string-based approaches with proper tools

Sprint 15 — TLC tool mode and PlusCal emitter compat:

  • [x] TLC -tool mode output parsing (replaces regex stdout scraping; JSON unavailable in TLC 2.19)
  • [x] PlusCal C-syntax emitter fixed for pcal.trans acceptance
  • [x] PlusCal P-syntax emitter (begin/end style)
  • [x] Integration tested: PlusCal → pcal.trans → TLC

Sprint 16 — Proper parsers and AST-based code gen:

Phase 11: Refinement and Verification (complete)

Target: v0.2.8 Focus: Spec-vs-spec comparison, emitter robustness

  • [x] refines DSL block with mapping entities
  • [x] TLA+ INSTANCE/WITH emission for refinement checking
  • [x] Auto-declare atom model values as CONSTANTS (TLA+ and .cfg)
  • [x] Fix branched action TLA+ emission (UNCHANGED inside disjunctions)
  • [x] Handle 3-tuple AST forms for ite/let_in/set ops inside e()
  • [x] Auto-include abstract spec atoms in INSTANCE identity mappings
  • [x] formal-spec skill — workflow from ADR to refinement-checked specs

Phase 10: Documentation (complete)

Target: v0.2.9 Focus: Diátaxis documentation and reference

  • [x] How-to guides (model GenServer, find race conditions, run TLC, verify with refinement)
  • [x] Explanation pages (why formal verification, TLX vs raw TLA+, formal specs vs testing)
  • [x] Getting-started tutorial rewrite (traffic light example)
  • [x] Reference docs (DSL, mix tasks, expressions)
  • [x] CONTRIBUTING.md with tone guidelines
  • [x] FAQ.md

Phase 12: Expressiveness II (complete)

Target: v0.2.11 Focus: Functions, maps, records, sequences

  • [x] Function application (at/2) and update (except/3)
  • [x] CHOOSE, set comprehension (filter/3), CASE (case_of/1)
  • [x] if syntax inside e(), let_in block style
  • [x] DOMAIN, range sets, implication/equivalence
  • [x] Sequence operations (len, append, head, tail, sub_seq)
  • [x] Configurable EXTENDS (extends [:Sequences])
  • [x] Record construction (record/1), multi-key EXCEPT (except_many/2)
  • [x] Symbols emitter (TLX DSL with math notation)

Phase 13: Quality and Supply Chain (complete)

Target: v0.3.1 Focus: Toolchain validation, supply chain security, emitter fixes

  • [x] SANY and pcal.trans toolchain validation (87 integration tests)
  • [x] AllConstructs comprehensive spec covering every DSL construct
  • [x] Emitter fixes: map defaults, atom collection, multi-action PlusCal
  • [x] SPDX copyright and license headers on all source files
  • [x] SECURITY.md, GitHub Actions CI, Dependabot
  • [x] ADRs 4-10 documenting architectural decisions
  • [x] mix tlx.list and mix tlx.watch
  • [x] Published on Hex.pm

Sprint History

SprintPhaseVersionSummary
Releasev0.5.0Squash release: round-trip track (sprints 54–59) + handoff polish (sprints 53, 60–67)
53Qualityv0.5.0Fix docs build warnings — 12 struct moduledocs + 4 DSL internals + prose rewrites
67Round-Trip Polishv0.5.0Binder canonical shape at property root — forall/exists/choose peel like temporal
66Round-Trip Polishv0.5.0Atom round-trip fidelity — CONSTANT identifiers re-emit as :atom literals
64Round-Trip Polishv0.5.0Quantifier short forms — accept \E x : P, \A x : P, CHOOSE x : P
63Round-Trip Polishv0.5.0Property codegen canonical shape — outer temporal peeled, e() around predicate only
61DXv0.5.0Fallback logging + mix tlx.import --verbose — tier-2 parse gaps now visible
62Round-Trip Polishv0.5.0TLA+ comment stripping in TlaParser — \* and (* *) (nestable) handled
60Qualityv0.5.0Fix nested e() emission — one format_ast clause unwraps {:e, meta, [arg]}
59Round-Tripv0.5.0Round-trip matrix + CI gate — ADR-0013 CI tripwire on 63 curated emit-side constructs
58Round-Tripv0.5.0CASE + temporal operators in property position — property classifier is AST-informed
57Round-Tripv0.5.0Sequences + LAMBDA (SelectSeq-scoped) — 8 sequence ops + contextual LAMBDA parse
56Round-Tripv0.5.0Arithmetic extensions, tuples, Cartesian, function ctor/set — 9 parse-side additions
55Round-Tripv0.5.0Sets, quantifiers, records, EXCEPT — 21 constructs parse to structured AST
54Round-Tripv0.5.0Expression parser foundation — NimbleParsec producing {:expr, ast} for the subset
49Expressivenessselect_seq(:var, s, pred)SelectSeq(s, LAMBDA var: pred); first LAMBDA emission
52ExpressivenessFunction ctor [x \in S |-> expr], function set [S -> T], Cartesian product \X
50Simulatorcase_of find_value fix — matched clause with false/nil body now wins
51ExpressivenessArithmetic completion: integer div, rem (%), ** (^), unary -
48SimulatorAST-form eval for 24 set/sequence/function/logic ops inside e()
47ExpressivenessSet/seq/tuple gaps: difference, set_map, power_set, UNION, concat, Seq, tuple
46Expressivenessuntil(p, q) and weak_until(p, q) — TLA+ \U, \W
45Expressivenesscase/do inside e() — emits TLA+ CASE ... [] OTHER
Releasev0.4.5mix tlx.check uses TLA+ emission directly (not PlusCal)
Releasev0.4.4Bug fixes: atom mismatch, simulator ite/case_of/let_in
Releasev0.4.3TLA+ references, pattern examples, roadmap sprints 44–47
Releasev0.4.2Graph refactor, C4 model, CONTRIBUTING, examples
43Documentationv0.4.1Diátaxis docs for v0.4.0 features
Releasev0.4.0Squash release: Sprints 26–42 + credo/dialyzer fixes
42Extractorsv0.3.17Broadway extractor — pipeline topology via AST
41Extractorsv0.3.16Reactor extractor — step DAG via Spark introspection
38-40Skillsv0.3.15spec-audit, visualize, spec-drift skills
37Skillv0.3.14formal-spec skill enrichment workflow
36Extractorsv0.3.13Ash.StateMachine extractor via runtime introspection
35Extractorsv0.3.12Erlang BEAM extractors — gen_server + gen_fsm
34Extractorsv0.3.11LiveView AST extractor + mix task + codegen
33Visualizationv0.3.10D2 state diagram emitter
32Visualizationv0.3.9PlantUML state diagram emitter
30Extractorsv0.3.8GenServer AST extractor + mix task + codegen
31OTP Patternsv0.3.7Supervisor pattern — restart strategies + escalation
29OTP Patternsv0.3.6GenServer pattern — request/response handler model
28Extractorsv0.3.5gen_statem AST extractor, ADR-0012 accepted
27OTP Patternsv0.3.4StateMachine pattern — reusable gen_statem template
26Visualizationv0.3.4Mermaid emitter — diagrams in GitHub markdown
25Visualizationv0.3.3GraphViz DOT emitter, example diagrams
24Documentationv0.3.2Internals docs for contributors
23Documentationv0.3.1Sprint index, missing retros, TlaParser subset doc
22Qualityv0.3.1SANY/pcal.trans validation, emitter bug fixes
11Toolingv0.3.1mix tlx.list, mix tlx.watch, Mix task naming fix
Supply Chainv0.3.0SPDX headers, SECURITY.md, CI, Dependabot
21Expressiveness IIv0.2.11Records, multi-EXCEPT, Symbols emitter, FAQ
20Expressiveness IIv0.2.10Sequences, DOMAIN, range, implies/equiv, extends
19Expressiveness IIv0.2.9at/except, CHOOSE, filter, CASE, if-syntax DX
18Documentationv0.2.9Reference docs: DSL, mix tasks, expressions
17Documentationv0.2.9How-tos, explanations, getting-started rewrite
Refinementv0.2.8Refinement DSL, emitter fixes, formal-spec skill
10Expressivenessv0.2.7ite, sets, let_in, custom init, pick from sets
16Robustnessv0.2.6NimbleParsec parsers, AST codegen, round-trip tests
15Robustnessv0.2.5TLC tool mode, PlusCal pcal.trans compat, P-syntax
14Qualityv0.2.4TLC integration testing against real subprocess
12Integrationv0.2.3TLA+ importer, GenStateMachine generator
13Validationv0.2.22PC and Raft examples, simulator found Raft bugs
9Semantic Intelligencev0.2.1Auto TypeOK, empty action warning, better errors
8DX Overhaulv0.2.0e() macro, flat sections, await, defspec, emitters
7Production Readyv0.1.7Examples, tutorial, Hex prep, edge case tests
6Simulation/Toolingv0.1.6Trace formatting, Spark docs generation
5Simulation/Toolingv0.1.5Mutex example, Elixir simulator
4PlusCal/Concurrencyv0.1.4Temporal properties, fairness, quantifiers
3PlusCal/Concurrencyv0.1.3Processes, TLC integration, config generation
2Foundation/PlusCalv0.1.2PlusCal emitter, either/or, mix tlx.emit task
1Foundationv0.1.1Core DSL (Spark), TLA+ emitter, quality gates

Proposed Sprints

SprintPhasePlan
44ToolingState/transition coverage — verify ExUnit tests exercise all spec states

Sprint 44: State/Transition Coverage

Goal: Answer "do my tests exercise all the states and transitions my spec defines?"

TLC gives exhaustive spec coverage (proof). This feature gives test coverage against the spec — which states/transitions the ExUnit suite actually exercises at runtime.

Architecture:

Spec (TLX)              Implementation            Tests (ExUnit)
                                                     
                                                     
Graph.extract/2   State/Transition Map    Instrumentation hooks
                                                  
     Compare 
                         
                    Coverage Report

Instrumentation approaches by module type:

Module typeState accessHook mechanism
gen_statem:sys.get_state/1 returns {state, data}:sys.trace/2 or custom handle_event wrapper
GenServer:sys.get_state/1 returns state mapTelemetry events or :sys.trace/2
LiveViewsocket.assignsTest helper that captures assigns after each event
Ash.StateMachineResource attribute valueAsh notifications or after_action hooks

Deliverables:

  1. TLX.Coverage — test helper module

    • start_tracking(module, spec) — begins state/transition recording
    • stop_tracking(module) — returns %{visited_states, visited_transitions}
    • report(module, spec) — compares against spec, prints coverage table
  2. mix tlx.coverage — mix task that runs tests with tracking enabled

    • Reads spec → implementation mapping from # Source: headers
    • Instruments implementations during test run
    • Reports per-spec coverage after tests complete
  3. ExUnit integration:

    defmodule MyApp.ReconcilerTest do
      use ExUnit.Case
      use TLX.Coverage, spec: ReconcilerSpec, module: MyApp.Reconciler
    
      # Tests run normally — coverage tracked automatically
      test "check returns in_sync" do
        # ...
      end
    end
    # After: prints state/transition coverage report

Output format:

State Coverage: MyApp.Reconciler (spec: ReconcilerSpec)

States:
  :idle         (12 visits)
  :in_sync      (8 visits)
  :drifted      (4 visits)
  :error        NOT TESTED

Transitions:
  idle  in_sync (check/success)      (6 visits)
  idle  drifted (check/failure)      (4 visits)
  drifted  in_sync (apply/success)   (3 visits)
  drifted  drifted (apply/failure)   NOT TESTED

States: 3/4 (75%)   Transitions: 3/4 (75%)

Challenges:

  • GenServer state is opaque — need to map struct fields to spec variables
  • LiveView assigns change frequently — need to filter spec-relevant fields only
  • Transition detection requires comparing consecutive states, not just snapshots
  • gen_statem state_functions mode: state is the function name, not in :sys.get_state
  • Performance: :sys.trace adds overhead; only enable during test runs

Prerequisites: None — uses existing specs and the shared Graph extraction module. Independent of extractors.

Sprints 54–59: Round-Trip Track (ADR-0013) — shipped

Driver: ADR-0013 — importer round-trips TLX-emitted output losslessly; hand-written TLA+ stays best-effort.

All six sprints landed. Importer now parses 63 TLA+ constructs to structured Elixir AST (foundation, sets, quantifiers, records, EXCEPT, arithmetic, tuples, Cartesian, functions, sequences, SelectSeq LAMBDA, CASE, temporal operators). TLX.Importer.Codegen emits real e(...) calls instead of raw-string comments. CI gate at test/integration/emitter_coverage_test.exs asserts every emitted construct has a matching parser rule.

See per-sprint plans and retrospectives in docs/sprints/sprint-0054-*.md through sprint-0059-*.md for scope, design decisions, surprises, and handoffs.