wunderspec 0.129.3__tar.gz → 0.129.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. {wunderspec-0.129.3 → wunderspec-0.129.6}/PKG-INFO +35 -14
  2. wunderspec-0.129.6/README.md +92 -0
  3. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/cheatsheet.html +64 -61
  4. wunderspec-0.129.6/docs/user-references/wunderspec-five-minutes.md +114 -0
  5. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/payment.py +3 -2
  6. {wunderspec-0.129.3 → wunderspec-0.129.6}/pyproject.toml +1 -1
  7. {wunderspec-0.129.3 → wunderspec-0.129.6}/tests/README.md +7 -7
  8. {wunderspec-0.129.3 → wunderspec-0.129.6}/uv.lock +1 -1
  9. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/__init__.py +1 -1
  10. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/cli.py +10 -4
  11. wunderspec-0.129.3/README.md +0 -71
  12. {wunderspec-0.129.3 → wunderspec-0.129.6}/.devcontainer/Dockerfile +0 -0
  13. {wunderspec-0.129.3 → wunderspec-0.129.6}/.devcontainer/devcontainer.json +0 -0
  14. {wunderspec-0.129.3 → wunderspec-0.129.6}/.devcontainer/post-create.sh +0 -0
  15. {wunderspec-0.129.3 → wunderspec-0.129.6}/.flake8 +0 -0
  16. {wunderspec-0.129.3 → wunderspec-0.129.6}/.github/workflows/build.yml +0 -0
  17. {wunderspec-0.129.3 → wunderspec-0.129.6}/.github/workflows/convert-to-tla.yml +0 -0
  18. {wunderspec-0.129.3 → wunderspec-0.129.6}/.github/workflows/lint.yml +0 -0
  19. {wunderspec-0.129.3 → wunderspec-0.129.6}/.github/workflows/publish.yml +0 -0
  20. {wunderspec-0.129.3 → wunderspec-0.129.6}/.github/workflows/run-examples.yml +0 -0
  21. {wunderspec-0.129.3 → wunderspec-0.129.6}/.gitignore +0 -0
  22. {wunderspec-0.129.3 → wunderspec-0.129.6}/CONTRIBUTING.md +0 -0
  23. {wunderspec-0.129.3 → wunderspec-0.129.6}/LICENSE +0 -0
  24. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/README.md +0 -0
  25. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-avatar-github-1024.png +0 -0
  26. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-avatar-github-256.png +0 -0
  27. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-avatar-github-512.png +0 -0
  28. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-icon-circle-dark-1024.png +0 -0
  29. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-icon-circle-dark-512.png +0 -0
  30. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-icon-circle-transparent-1024.png +0 -0
  31. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-lockup-horizontal-dark-1600.png +0 -0
  32. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-lockup-horizontal-dark-800.png +0 -0
  33. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-mark-transparent-1024.png +0 -0
  34. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-readme-header-dark-1200x480.png +0 -0
  35. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-readme-header-dark-1600x640.png +0 -0
  36. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-social-preview-1280x640.png +0 -0
  37. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/png/wunderspec-wordmark-transparent-900.png +0 -0
  38. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-avatar-github.svg +0 -0
  39. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-icon-circle-dark.svg +0 -0
  40. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-icon-circle-transparent.svg +0 -0
  41. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-lockup-horizontal-dark.svg +0 -0
  42. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-lockup-horizontal-transparent.svg +0 -0
  43. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-mark-transparent.svg +0 -0
  44. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-readme-header-dark.svg +0 -0
  45. {wunderspec-0.129.3 → wunderspec-0.129.6}/assets/design/svg/wunderspec-wordmark-transparent.svg +0 -0
  46. {wunderspec-0.129.3 → wunderspec-0.129.6}/basedpyrightconfig.json +0 -0
  47. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/references/from-quint-llms.md +0 -0
  48. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/references/from-tla-llms.md +0 -0
  49. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/booleans.md +0 -0
  50. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/comprehensions.md +0 -0
  51. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/decorators.md +0 -0
  52. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/enums.md +0 -0
  53. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/flow.md +0 -0
  54. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/integers.md +0 -0
  55. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/lists.md +0 -0
  56. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/maps.md +0 -0
  57. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/records.md +0 -0
  58. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/sets.md +0 -0
  59. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/state-machine.md +0 -0
  60. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/strings.md +0 -0
  61. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/temporal.md +0 -0
  62. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/tuples.md +0 -0
  63. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-references/unions.md +0 -0
  64. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-stories/bobs_log.md +0 -0
  65. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-stories/bobs_log.png +0 -0
  66. {wunderspec-0.129.3 → wunderspec-0.129.6}/docs/user-stories/img/bob.png +0 -0
  67. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/bags.py +0 -0
  68. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/bakery.py +0 -0
  69. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/bakery_walk.py +0 -0
  70. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/ben_or.py +0 -0
  71. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/channel.py +0 -0
  72. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/dekker.py +0 -0
  73. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/epfd.py +0 -0
  74. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/examples.yaml +0 -0
  75. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/fifo.py +0 -0
  76. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/fpaxos.py +0 -0
  77. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/kv_store.py +0 -0
  78. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/lamport_mutex.py +0 -0
  79. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/ledger.py +0 -0
  80. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/minimmit.py +0 -0
  81. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/producer_consumer.py +0 -0
  82. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/readers_writers.py +0 -0
  83. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/readers_writers_walk.py +0 -0
  84. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/simple_ponzi.py +0 -0
  85. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/simple_ponzi_machine.py +0 -0
  86. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/simple_ponzi_machine_walk.py +0 -0
  87. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/simple_wal1.py +0 -0
  88. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/simple_wal2.py +0 -0
  89. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/sliding_puzzles.py +0 -0
  90. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/sloppy_counter.py +0 -0
  91. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/test_simple_ponzi_machine.py +0 -0
  92. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/tiny_workers.py +0 -0
  93. {wunderspec-0.129.3 → wunderspec-0.129.6}/examples/two_phase.py +0 -0
  94. {wunderspec-0.129.3 → wunderspec-0.129.6}/pyrightconfig.json +0 -0
  95. {wunderspec-0.129.3 → wunderspec-0.129.6}/scripts/__init__.py +0 -0
  96. {wunderspec-0.129.3 → wunderspec-0.129.6}/scripts/_spec_utils.py +0 -0
  97. {wunderspec-0.129.3 → wunderspec-0.129.6}/scripts/convert_examples_to_tla.py +0 -0
  98. {wunderspec-0.129.3 → wunderspec-0.129.6}/scripts/run_examples.py +0 -0
  99. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/__main__.py +0 -0
  100. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/_edition.py +0 -0
  101. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/apalache.py +0 -0
  102. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/api.py +0 -0
  103. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/__init__.py +0 -0
  104. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/action_ast.py +0 -0
  105. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/ast.py +0 -0
  106. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/list_ast.py +0 -0
  107. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/map_ast.py +0 -0
  108. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/record_ast.py +0 -0
  109. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/serialization.py +0 -0
  110. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/set_ast.py +0 -0
  111. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/sorts.py +0 -0
  112. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/temporal_ast.py +0 -0
  113. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/terms.py +0 -0
  114. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/tuple_ast.py +0 -0
  115. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/ast/union_ast.py +0 -0
  116. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/dev_debug.py +0 -0
  117. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/doc_format.py +0 -0
  118. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/errors.py +0 -0
  119. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/exec/__init__.py +0 -0
  120. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/exec/action_exec.py +0 -0
  121. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/exec/context.py +0 -0
  122. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/exec/scheduler.py +0 -0
  123. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/explain.py +0 -0
  124. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/expr.py +0 -0
  125. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/flow.py +0 -0
  126. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/fuzzer.py +0 -0
  127. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/interpreter.py +0 -0
  128. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/interpreter_sampling.py +0 -0
  129. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/interpreter_value.py +0 -0
  130. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/itf_trace.py +0 -0
  131. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/lang.py +0 -0
  132. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/linter.py +0 -0
  133. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/machine.py +0 -0
  134. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/model_checker.py +0 -0
  135. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/permutation.py +0 -0
  136. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/petnames.py +0 -0
  137. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/pretty.py +0 -0
  138. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/py.typed +0 -0
  139. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/quint_convert.py +0 -0
  140. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/random_walk.py +0 -0
  141. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/source_tracking.py +0 -0
  142. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/submachine.py +0 -0
  143. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/sym_context.py +0 -0
  144. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/tla.py +0 -0
  145. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/tlc.py +0 -0
  146. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/tlc_trace.py +0 -0
  147. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/trace_output.py +0 -0
  148. {wunderspec-0.129.3 → wunderspec-0.129.6}/wunderspec/uniq_names.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wunderspec
3
- Version: 0.129.3
3
+ Version: 0.129.6
4
4
  Summary: Protocol specifications as Python code
5
5
  Author: Igor Konnov, Thomas Pani
6
6
  License-File: LICENSE
@@ -18,6 +18,12 @@ Description-Content-Type: text/markdown
18
18
 
19
19
  # Wunderspec
20
20
 
21
+ [![PyPI](https://img.shields.io/pypi/v/wunderspec.svg)](https://pypi.org/project/wunderspec/)
22
+ [![Lint](https://github.com/wunderspec/wunderspec/actions/workflows/lint.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/lint.yml)
23
+ [![Build](https://github.com/wunderspec/wunderspec/actions/workflows/build.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/build.yml)
24
+ [![Run Examples](https://github.com/wunderspec/wunderspec/actions/workflows/run-examples.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/run-examples.yml)
25
+ [![Convert to TLA+](https://github.com/wunderspec/wunderspec/actions/workflows/convert-to-tla.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/convert-to-tla.yml)
26
+
21
27
  Wunderspec is a Python DSL for writing and checking executable specifications.
22
28
 
23
29
  Distributed systems often fail due to unforeseen issues: message reordering,
@@ -29,21 +35,24 @@ they become production incidents.
29
35
 
30
36
  This is **the open-core distribution**.
31
37
 
32
- > 📋 **Cheatsheet:** the [Wunderspec cheatsheet](https://github.com/wunderspec/wunderspec/blob/main/docs/user-references/cheatsheet.html)
33
- > summarizes the DSL and CLI on a single page keep it open while you work.
38
+ > 📋 **Wunderspec in 5 minutes:** the [Wunderspec in Five Minutes][five-minutes]
39
+ > gives you a quick overview of the core concepts.
40
+
41
+ > 📋 **Cheatsheet:** the [Wunderspec cheatsheet][cheatsheet]
42
+ > summarizes the DSL and CLI in a few pages — keep it open while you work.
34
43
 
35
44
  ## 1. Wunderspec in Action
36
45
 
37
- Follow Bob in [his Wunderspec adventure](https://github.com/wunderspec/wunderspec/blob/main/docs/user-stories/bobs_log.md). There,
38
- Bob writes a specification of a write-ahead log, finds a bug, replays the
39
- counterexample and fixes the specification.
46
+ Follow Bob in [his Wunderspec adventure][bobs_log]. There, Bob writes a
47
+ specification of a write-ahead log, finds a bug, replays the counterexample and
48
+ fixes the specification.
40
49
 
41
50
  ## 2. Installation
42
51
 
43
- Using [uv](https://docs.astral.sh/uv/):
52
+ Using [uv][]:
44
53
 
45
54
  ```sh
46
- uv add wunderspec
55
+ uv tool install wunderspec
47
56
  ```
48
57
 
49
58
  After installation, the CLI is available:
@@ -52,6 +61,13 @@ After installation, the CLI is available:
52
61
  wunderspec --help
53
62
  ```
54
63
 
64
+ If you want to add Wunderspec as a dependency, just type:
65
+
66
+ ```sh
67
+ uv add wunderspec
68
+ uv sync
69
+ ```
70
+
55
71
  ## 3. Features
56
72
 
57
73
  This open-core package includes the features marked ✅ in the **Open Core**
@@ -74,14 +90,19 @@ commands are not included in this package.
74
90
 
75
91
  ## 4. Release Provenance
76
92
 
77
- - Release tag: `v0.129.3`
78
- - Source commit: `b21d0bd6cd54d3e259e418626727410c4875ac0b`
93
+ - Release tag: `v0.129.6`
94
+ - Source commit: `ab0a7ebd149ec98095c87f190c8c43616c1df187`
79
95
 
80
- See [tests/README.md](https://github.com/wunderspec/wunderspec/blob/main/tests/README.md)
81
- for the development test log captured at release time.
96
+ See [tests/README.md][] for the development test log captured at release time.
82
97
 
83
98
  ## 5. License
84
99
 
85
100
  Wunderspec is distributed under the Functional Source License, Version 1.1, with
86
- an Apache 2.0 future license (FSL-1.1-ALv2). See
87
- [LICENSE](https://github.com/wunderspec/wunderspec/blob/main/LICENSE).
101
+ an Apache 2.0 future license (FSL-1.1-ALv2). See [LICENSE][].
102
+
103
+ [cheatsheet]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-references/cheatsheet.html
104
+ [five-minutes]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-references/wunderspec-five-minutes.md
105
+ [bobs_log]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-stories/bobs_log.md
106
+ [uv]: https://docs.astral.sh/uv/
107
+ [tests/README.md]: https://github.com/wunderspec/wunderspec/blob/main/tests/README.md
108
+ [LICENSE]: https://github.com/wunderspec/wunderspec/blob/main/LICENSE
@@ -0,0 +1,92 @@
1
+ ![Wunderspec](https://raw.githubusercontent.com/wunderspec/wunderspec/main/assets/design/png/wunderspec-readme-header-dark-1200x480.png)
2
+
3
+ # Wunderspec
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/wunderspec.svg)](https://pypi.org/project/wunderspec/)
6
+ [![Lint](https://github.com/wunderspec/wunderspec/actions/workflows/lint.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/lint.yml)
7
+ [![Build](https://github.com/wunderspec/wunderspec/actions/workflows/build.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/build.yml)
8
+ [![Run Examples](https://github.com/wunderspec/wunderspec/actions/workflows/run-examples.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/run-examples.yml)
9
+ [![Convert to TLA+](https://github.com/wunderspec/wunderspec/actions/workflows/convert-to-tla.yml/badge.svg)](https://github.com/wunderspec/wunderspec/actions/workflows/convert-to-tla.yml)
10
+
11
+ Wunderspec is a Python DSL for writing and checking executable specifications.
12
+
13
+ Distributed systems often fail due to unforeseen issues: message reordering,
14
+ race condition, stale observations, retry behavior, crashes and restarts,
15
+ network outages, vibe code. Adding more example tests is often not enough to
16
+ find these bugs before they make it to production. Specifications written in
17
+ Wunderspec surface these issues early, so that teams find design flaws before
18
+ they become production incidents.
19
+
20
+ This is **the open-core distribution**.
21
+
22
+ > 📋 **Wunderspec in 5 minutes:** the [Wunderspec in Five Minutes][five-minutes]
23
+ > gives you a quick overview of the core concepts.
24
+
25
+ > 📋 **Cheatsheet:** the [Wunderspec cheatsheet][cheatsheet]
26
+ > summarizes the DSL and CLI in a few pages — keep it open while you work.
27
+
28
+ ## 1. Wunderspec in Action
29
+
30
+ Follow Bob in [his Wunderspec adventure][bobs_log]. There, Bob writes a
31
+ specification of a write-ahead log, finds a bug, replays the counterexample and
32
+ fixes the specification.
33
+
34
+ ## 2. Installation
35
+
36
+ Using [uv][]:
37
+
38
+ ```sh
39
+ uv tool install wunderspec
40
+ ```
41
+
42
+ After installation, the CLI is available:
43
+
44
+ ```sh
45
+ wunderspec --help
46
+ ```
47
+
48
+ If you want to add Wunderspec as a dependency, just type:
49
+
50
+ ```sh
51
+ uv add wunderspec
52
+ uv sync
53
+ ```
54
+
55
+ ## 3. Features
56
+
57
+ This open-core package includes the features marked ✅ in the **Open Core**
58
+ column. Premium-only command names stay visible for discoverability, but those
59
+ commands are not included in this package.
60
+
61
+ | Feature | Open Core | Premium |
62
+ | ----------------------------- | :-------: | :-----: |
63
+ | Symbolic expression evaluator | ✅ | ✅ |
64
+ | `wunderspec lint` | ✅ | ✅ |
65
+ | `wunderspec run` | ✅ | ✅ |
66
+ | `wunderspec replay` | ✅ | ✅ |
67
+ | `wunderspec convert` | ✅ | ✅ |
68
+ | `wunderspec check` | ✅ | ✅ |
69
+ | `wunderspec with-tlc` | ✅ | ✅ |
70
+ | `wunderspec with-apalache` | ✅ | ✅ |
71
+ | `wunderspec fuzz` | ❌ | ✅ |
72
+ | `wunderspec rust` | ❌ | ✅ |
73
+ | `wunderspec lean` | ❌ | ✅ |
74
+
75
+ ## 4. Release Provenance
76
+
77
+ - Release tag: `v0.129.6`
78
+ - Source commit: `ab0a7ebd149ec98095c87f190c8c43616c1df187`
79
+
80
+ See [tests/README.md][] for the development test log captured at release time.
81
+
82
+ ## 5. License
83
+
84
+ Wunderspec is distributed under the Functional Source License, Version 1.1, with
85
+ an Apache 2.0 future license (FSL-1.1-ALv2). See [LICENSE][].
86
+
87
+ [cheatsheet]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-references/cheatsheet.html
88
+ [five-minutes]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-references/wunderspec-five-minutes.md
89
+ [bobs_log]: https://github.com/wunderspec/wunderspec/blob/main/docs/user-stories/bobs_log.md
90
+ [uv]: https://docs.astral.sh/uv/
91
+ [tests/README.md]: https://github.com/wunderspec/wunderspec/blob/main/tests/README.md
92
+ [LICENSE]: https://github.com/wunderspec/wunderspec/blob/main/LICENSE
@@ -547,15 +547,13 @@
547
547
  </div>
548
548
  <div class="desc">Boolean equality / inequality</div>
549
549
  </div>
550
- <div class="item">
551
- <div class="sig">Ite(cond, then_expr, else_expr)</div>
552
- <div class="desc">If-then-else expression; all three arguments are expressions</div>
553
- </div>
554
550
  <div class="item">
555
551
  <div class="alt">
552
+ <span class="sig">Ite(cond, then_expr, else_expr)</span>
553
+ <span class="sep">or</span>
556
554
  <span class="sig">p.if_(cond).else_(q)</span>
557
- </div>
558
- <div class="desc">Method-chain form of <span class="sig">Ite(cond, p, q)</span></div>
555
+ </div>
556
+ <div class="desc">If-then-else expression; all three arguments are expressions</div>
559
557
  </div>
560
558
  </div>
561
559
  </section>
@@ -575,13 +573,17 @@
575
573
  <div class="sig">x == y x != y x &lt; y x &lt;= y x &gt; y x &gt;= y</div>
576
574
  <div class="desc">Comparison operators; return <span class="sig">BoolExpr</span></div>
577
575
  </div>
576
+ <div class="item">
577
+ <span class="sig">Set(lo, ..., hi)</span>
578
+ <div class="desc">Inclusive integer set <span class="sig">{lo, …, hi}</span>; distinct from list <span class="sig">Range(start, end)</span></div>
579
+ </div>
578
580
  <div class="item">
579
581
  <div class="alt">
580
- <span class="sig">Interval(lo, hi)</span>
581
- <span class="sep">or</span>
582
- <span class="sig">lo.upto(hi)</span>
582
+ <span class="sig">Ints</span>
583
+ <span class="sep">or</span>
584
+ <span class="sig">UnsignedInts</span>
583
585
  </div>
584
- <div class="desc">Inclusive integer set <span class="sig">{lo, …, hi}</span>; distinct from list <span class="sig">Range(start, end)</span></div>
586
+ <div class="desc">All signed / unsigned integers (infinite sets)</div>
585
587
  </div>
586
588
  </div>
587
589
  </section>
@@ -594,16 +596,16 @@
594
596
  </header>
595
597
  <div class="content">
596
598
  <div class="item">
597
- <div class="sig">Set(1, 2, 3)</div>
598
- <div class="desc">New set with the given elements</div>
599
+ <div class="sig">Set(2, 5, 8, 8)</div>
600
+ <div class="desc">New set with the given elements, duplicates ignored</div>
599
601
  </div>
600
602
  <div class="item">
601
- <div class="sig">Set(1, ..., 4)</div>
602
- <div class="desc">Shorthand: <span class="sig">Set(1, 2, 3, 4)</span></div>
603
+ <div class="sig">Set(lo, ..., hi)</div>
604
+ <div class="desc">Inclusive integer set <span class="sig">{lo, …, hi}</span></div>
603
605
  </div>
604
606
  <div class="item">
605
607
  <div class="sig">Set(E)</div>
606
- <div class="desc">Empty set whose elements have type <span class="sig">E</span></div>
608
+ <div class="desc">Empty set whose elements have sort <span class="sig">E</span></div>
607
609
  </div>
608
610
  <div class="item">
609
611
  <div class="alt">
@@ -746,12 +748,12 @@
746
748
  </header>
747
749
  <div class="content">
748
750
  <div class="item">
749
- <div class="sig">List(1, 2, 3)</div>
750
- <div class="desc">New list with the given elements in order</div>
751
+ <div class="sig">List(2, 3, 3, 4)</div>
752
+ <div class="desc">New list with the given elements in order, duplicates allowed</div>
751
753
  </div>
752
754
  <div class="item">
753
755
  <div class="sig">List(E)</div>
754
- <div class="desc">Empty list with elements of type <span class="sig">E</span></div>
756
+ <div class="desc">Empty list with elements of sort <span class="sig">E</span></div>
755
757
  </div>
756
758
  <div class="item">
757
759
  <div class="sig">Range(start, end)</div>
@@ -835,7 +837,7 @@
835
837
  </div>
836
838
  <div class="item">
837
839
  <div class="sig">M[key]</div>
838
- <div class="desc">Look up value for <span class="sig">key</span></div>
840
+ <div class="desc">Look up value for <span class="sig">key</span> (<span class="sig">key</span> must exist)</div>
839
841
  </div>
840
842
  <div class="item">
841
843
  <div class="sig">M.insert(k, v)</div>
@@ -1069,6 +1071,49 @@ class Flag:
1069
1071
  </div>
1070
1072
  </section>
1071
1073
 
1074
+ <!-- Temporal -->
1075
+ <section class="card" data-title="temporal always eventually enabled weakfair strongfair liveness safety action_expr forall exists fairness">
1076
+ <header>
1077
+ <h2>Temporal expressions</h2>
1078
+ <span class="hint">TemporalExpr — used in @temporal properties</span>
1079
+ </header>
1080
+ <div class="content">
1081
+ <div class="item">
1082
+ <div class="sig">Always(P)</div>
1083
+ <div class="desc">□P — <span class="sig">P</span> holds in every state of the execution</div>
1084
+ </div>
1085
+ <div class="item">
1086
+ <div class="sig">Eventually(P)</div>
1087
+ <div class="desc">◇P — <span class="sig">P</span> holds in some future state</div>
1088
+ </div>
1089
+ <div class="item">
1090
+ <div class="sig">AndT(t1, t2) OrT(t1, t2) NotT(t) ImpliesT(t1, t2)</div>
1091
+ <div class="desc">Temporal connectives; Boolean arguments are lifted to <span class="sig">TemporalExpr</span></div>
1092
+ </div>
1093
+ <div class="item">
1094
+ <div class="sig">Enabled(my_action, arg)</div>
1095
+ <div class="desc">True if <span class="sig">my_action</span> can fire; requires <span class="sig">@action(inline=False)</span></div>
1096
+ </div>
1097
+ <div class="item">
1098
+ <div class="sig">Enabled(action_expr)</div>
1099
+ <div class="desc">Direct action-expression form of <span class="sig">ENABLED A</span></div>
1100
+ </div>
1101
+ <div class="item">
1102
+ <div class="sig">WeakFair(my_action, arg, vars=("x",))</div>
1103
+ <div class="desc">WF — if action is continuously enabled it must eventually fire</div>
1104
+ </div>
1105
+ <div class="item">
1106
+ <div class="sig">StrongFair(my_action, arg, vars=("x",))</div>
1107
+ <div class="desc">SF — if action is enabled infinitely often it must eventually fire</div>
1108
+ </div>
1109
+ <div class="item">
1110
+ <div class="sig">Forall(WeakFair(send, p, vars=("msgs",)) for p in PROCS)</div>
1111
+ <div class="desc">Temporal quantification over a parameter set</div>
1112
+ </div>
1113
+ </div>
1114
+ </section>
1115
+
1116
+
1072
1117
  <!-- State machine -->
1073
1118
  <section class="card" style="grid-column:span 12" data-title="state machine action context one_of alternatives split assume cache Param StateVar PARAMETER StateFieldType UPSERT direct assignment nested editing atomic">
1074
1119
  <header>
@@ -1277,48 +1322,6 @@ InChan = SubMachine(Data="Message", chan="cin")
1277
1322
  </div>
1278
1323
  </section>
1279
1324
 
1280
- <!-- Temporal -->
1281
- <section class="card" data-title="temporal always eventually enabled weakfair strongfair liveness safety action_expr forall exists fairness">
1282
- <header>
1283
- <h2>Temporal expressions</h2>
1284
- <span class="hint">TemporalExpr — used in @temporal properties</span>
1285
- </header>
1286
- <div class="content">
1287
- <div class="item">
1288
- <div class="sig">Always(P)</div>
1289
- <div class="desc">□P — <span class="sig">P</span> holds in every state of the execution</div>
1290
- </div>
1291
- <div class="item">
1292
- <div class="sig">Eventually(P)</div>
1293
- <div class="desc">◇P — <span class="sig">P</span> holds in some future state</div>
1294
- </div>
1295
- <div class="item">
1296
- <div class="sig">AndT(t1, t2) OrT(t1, t2) NotT(t) ImpliesT(t1, t2)</div>
1297
- <div class="desc">Temporal connectives; Boolean arguments are lifted to <span class="sig">TemporalExpr</span></div>
1298
- </div>
1299
- <div class="item">
1300
- <div class="sig">Enabled(my_action, arg)</div>
1301
- <div class="desc">True if <span class="sig">my_action</span> can fire; requires <span class="sig">@action(inline=False)</span></div>
1302
- </div>
1303
- <div class="item">
1304
- <div class="sig">Enabled(action_expr)</div>
1305
- <div class="desc">Direct action-expression form of <span class="sig">ENABLED A</span></div>
1306
- </div>
1307
- <div class="item">
1308
- <div class="sig">WeakFair(my_action, arg, vars=("x",))</div>
1309
- <div class="desc">WF — if action is continuously enabled it must eventually fire</div>
1310
- </div>
1311
- <div class="item">
1312
- <div class="sig">StrongFair(my_action, arg, vars=("x",))</div>
1313
- <div class="desc">SF — if action is enabled infinitely often it must eventually fire</div>
1314
- </div>
1315
- <div class="item">
1316
- <div class="sig">Forall(WeakFair(send, p, vars=("msgs",)) for p in PROCS)</div>
1317
- <div class="desc">Temporal quantification over a parameter set</div>
1318
- </div>
1319
- </div>
1320
- </section>
1321
-
1322
1325
  <!-- Imperative flow -->
1323
1326
  <section class="card" data-title="flow with_flow if else return end imperative conditional">
1324
1327
  <header>
@@ -0,0 +1,114 @@
1
+ # Wunderspec in Five Minutes
2
+
3
+ Since nobody has time to read docs, here is a one-page summary of what you want
4
+ to know about Wunderspec. For more details, see the [Cheatsheet][cheatsheet].
5
+
6
+ **States and actions**. The most important definitions are annotated with
7
+ `@state` and `@action`. Wunderspec uses these definitions to define a *state
8
+ machine*. This state machine starts in a state defined with
9
+ `@action(init=True)` and evolves via one or more actions labeled with `@action`.
10
+ Normally, the top-level actions are grouped with an action called `step`. All
11
+ actions are atomic -- either an action executes (when all assumptions are met),
12
+ and its effects are applied, or it doesn't execute at all. For a simple example,
13
+ see the [sloppy counter][sloppy_counter].
14
+
15
+ **Assumptions and assignments**. Actions normally contain assumptions about
16
+ the current state and assignments to the next state. Here is a typical example
17
+ from the [ledger][ledger]:
18
+
19
+ ```python
20
+ @action
21
+ def start_transaction(c: Context[LedgerState], company: Annotated[Expr, str]):
22
+ s = c.state
23
+ c.assume(s.pending_seq_no[company] == -1)
24
+ s.pending_seq_no[company] = s.registered_seq_no[company]
25
+ s.registered_seq_no[company] += 1
26
+ ```
27
+
28
+ The Wunderspec transpiler collects the assumptions and assignments, when the
29
+ Python code is executed. They are used to define the state machine.
30
+
31
+ **Every variable of a state machine may be assigned at most once in an action**.
32
+ Use local Python variables to compute intermediate values.
33
+
34
+ **Value generation**. State machines in Wunderspec often use
35
+ non-deterministically generated values. These values are produced with `with
36
+ c.one_of(...)`. We can see an example in the [sloppy counter][sloppy_counter]:
37
+
38
+ ```python
39
+ @action
40
+ def step(c: Context[SloppyCounterState]):
41
+ with c.one_of(cpus(c.state), "cpu") as cpu:
42
+ increment(c, cpu)
43
+ ```
44
+
45
+ In the code above, `cpu` receive a value from the set `cpus(c.state)`. The
46
+ second argument to `one_of` is a convenient alias for the intermediate
47
+ representation and generated code. When it's omitted, the name is generated
48
+ automatically. Wunderspec commands interpret this construct differently:
49
+
50
+ - `wunderspec run` generates values randomly,
51
+
52
+ - `wunderspec check` generates values systematically to explore all possible
53
+ executions,
54
+
55
+ - `wunderspec with-apalache` produces values with the SMT solver Z3.
56
+
57
+ **Control non-determinism.** Wunderspec does not have explicit processes,
58
+ threads, processors, or other computing devices. Instead, is uses control
59
+ non-determinism to model choice of actions. Non-deterministic choice is done
60
+ with `with c.alternatives(...)`. For example, he is how [simple
61
+ WAL][simple_wal1] defines `step` with alternatives:
62
+
63
+ ```python
64
+ @action
65
+ def step(c: Context[KvStoreState]):
66
+ run, crash = c.alternatives("Run", "Restart")
67
+ with run:
68
+ kv_step(c)
69
+ with crash:
70
+ s = c.state
71
+ s.pc = Val(PC.Start)
72
+ s.pending = Command.Nop()
73
+ s.kv_mem = s.KEYS.map_to(lambda _: Val(0))
74
+ ```
75
+
76
+ In the above code, the call to `c.alternatives` introduces two branching
77
+ alternatives, "Run" and "Restart". Wunderspec commands interpret these
78
+ alternatives differently:
79
+
80
+ - `wunderspec run` chooses one of the alternatives randomly,
81
+
82
+ - `wunderspec check` explores all possible alternatives systematically,
83
+
84
+ - `wunderspec with-apalache` explores the alternatives symbolically.
85
+
86
+ Similar to `c.alternatives`, we have a special version `c.split(condition)` that
87
+ introduces two alternatives, one where `condition` holds and another where it
88
+ doesn't hold.
89
+
90
+ **Properties.** Properties are the targets for analysis tools. Wunderspec
91
+ currently has three kinds of properties:
92
+
93
+ - state `@invariant` is a Boolean condition over a state that must hold in all
94
+ states reachable from the initial states. All tools check state invariants. If
95
+ they find a state that violates the invariant, they report a counterexample
96
+ execution.
97
+
98
+ - state `@example` is a Boolean condition over a state that maybe holds in some
99
+ execution of the state machine. Tools report one or more executions that lead
100
+ to a state that satisfies the example.
101
+
102
+ - `@temporal` properties must hold in all executions. They are typically used
103
+ to check complex safety properties and liveness properties. Currently, only
104
+ `with-tlc` and `with-apalache` check temporal properties.
105
+
106
+ **Immutability.** While the Wunderspec state machines mutate the state,
107
+ the expressions operate over immutable data structures. For the data
108
+ structures, see the [Cheatsheet][cheatsheet].
109
+
110
+
111
+ [sloppy_counter]: https://github.com/wunderspec/wunderspec/blob/main/examples/sloppy_counter.py
112
+ [ledger]: https://github.com/wunderspec/wunderspec/blob/main/examples/ledger.py
113
+ [simple_wal1]: https://github.com/wunderspec/wunderspec/blob/main/examples/simple_wal1.py
114
+ [cheatsheet]: https://wunderspec.com/cheatsheet
@@ -15,6 +15,7 @@ from wunderspec import (
15
15
  Exists,
16
16
  Expr,
17
17
  Field,
18
+ Forall,
18
19
  List,
19
20
  Map,
20
21
  Param,
@@ -394,12 +395,12 @@ def type_invariant(s: PaymentState) -> BoolExpr:
394
395
 
395
396
  @invariant
396
397
  def no_overdraft(s: PaymentState) -> BoolExpr:
397
- return accounts(s).forall(lambda account: s.balance[account] >= 0)
398
+ return Forall(s.balance[account] >= 0 for account in accounts(s))
398
399
 
399
400
 
400
401
  @invariant
401
402
  def no_double_charge(s: PaymentState) -> BoolExpr:
402
- return s.Txn.forall(lambda txn: event_count(s, txn, CHARGED) <= Val(1))
403
+ return Forall(event_count(s, txn, CHARGED) <= Val(1) for txn in s.Txn)
403
404
 
404
405
 
405
406
  @invariant
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wunderspec"
3
- version = "0.129.3"
3
+ version = "0.129.6"
4
4
  description = "Protocol specifications as Python code"
5
5
  authors = [
6
6
  {name = "Igor Konnov"},
@@ -2,13 +2,13 @@
2
2
 
3
3
  The unit tests from the Wunderspec development repository are omitted from this
4
4
  public distribution to keep the package focused. The full development
5
- `make test` suite currently collects 2354 tests.
5
+ `make test` suite currently collects 2355 tests.
6
6
 
7
7
  ## Release Provenance
8
8
 
9
- - Release tag: `v0.129.3`
10
- - Source commit: `b21d0bd6cd54d3e259e418626727410c4875ac0b`
11
- - Test log captured at: `2026-06-16T07:26:42Z`
9
+ - Release tag: `v0.129.6`
10
+ - Source commit: `ab0a7ebd149ec98095c87f190c8c43616c1df187`
11
+ - Test log captured at: `2026-06-16T19:21:00Z`
12
12
  - `make test` exit code: `0`
13
13
 
14
14
  ## Full Test Log From the Development Repository
@@ -20,7 +20,7 @@ platform linux -- Python 3.12.13, pytest-8.4.2, pluggy-1.6.0
20
20
  rootdir: /home/runner/work/wunderspec-dev/wunderspec-dev/release-source
21
21
  configfile: pyproject.toml
22
22
  plugins: pytest_codeblocks-0.17.0, subtests-0.15.0, hypothesis-6.155.2, cov-6.3.0, markdown-pytest-0.3.2
23
- collected 2354 items
23
+ collected 2355 items
24
24
 
25
25
  docs/user-references/booleans.md . [ 0%]
26
26
  docs/user-references/comprehensions.md . [ 0%]
@@ -112,7 +112,7 @@ tests/test_quint_translation_manifest.py ..... [ 87%]
112
112
  tests/test_random_walk_replay.py ..... [ 87%]
113
113
  tests/test_random_walk_seeds.py .... [ 87%]
114
114
  tests/test_record_decorator.py ............ [ 88%]
115
- tests/test_release_distribution.py ........... [ 88%]
115
+ tests/test_release_distribution.py ............ [ 88%]
116
116
  tests/test_schedule_enumerator.py ................... [ 89%]
117
117
  tests/test_serialization.py ...... [ 89%]
118
118
  tests/test_source_tracking.py ............. [ 90%]
@@ -124,5 +124,5 @@ tests/test_tla.py ...................................................... [ 94%]
124
124
  tests/test_tlc_trace.py .... [ 98%]
125
125
  tests/test_trace_output.py ......................... [100%]
126
126
 
127
- ====================== 2343 passed, 11 skipped in 25.63s =======================
127
+ ====================== 2344 passed, 11 skipped in 25.62s =======================
128
128
  ```
@@ -1781,7 +1781,7 @@ wheels = [
1781
1781
 
1782
1782
  [[package]]
1783
1783
  name = "wunderspec"
1784
- version = "0.129.3"
1784
+ version = "0.129.6"
1785
1785
  source = { editable = "." }
1786
1786
  dependencies = [
1787
1787
  { name = "executing" },
@@ -144,7 +144,7 @@ from .random_walk import state_view
144
144
  from .source_tracking import enable_source_tracking
145
145
  from .submachine import SubMachine
146
146
 
147
- __version__ = "0.129.3"
147
+ __version__ = "0.129.6"
148
148
 
149
149
  __all__ = [
150
150
  # Constructor functions
@@ -500,19 +500,23 @@ def main() -> None:
500
500
 
501
501
  convert_parser = subparsers.add_parser(
502
502
  "convert",
503
- help="Convert a Wunderspec module to another format",
503
+ help="Convert Wunderspec to TLA+ or Quint to Wunderspec",
504
+ description=(
505
+ "Convert a Wunderspec .py module to TLA+ .tla, "
506
+ "or a Quint .qnt module to Wunderspec .py."
507
+ ),
504
508
  )
505
509
  convert_parser.add_argument(
506
510
  "--from",
507
511
  dest="source",
508
512
  required=True,
509
- help="Source Python file containing the Wunderspec module",
513
+ help="Source specification file: Wunderspec .py or Quint .qnt",
510
514
  )
511
515
  convert_parser.add_argument(
512
516
  "--to",
513
517
  dest="output",
514
518
  required=True,
515
- help="Output file path (e.g., output.tla)",
519
+ help="Output file path: .tla for Wunderspec input, .py for Quint input",
516
520
  )
517
521
  convert_parser.add_argument(
518
522
  "--defs",
@@ -524,7 +528,9 @@ def main() -> None:
524
528
  "--instance",
525
529
  default=None,
526
530
  help="Name of a partially specialized state object in the source module "
527
- "(e.g., proto5). Generates an MC*_<Base> wrapper with fixed constants and INSTANCE.",
531
+ "(e.g., proto5). Applies only to Wunderspec-to-TLA+ conversion; "
532
+ "ignored for Quint input. Generates an MC*_<Base> wrapper with fixed "
533
+ "constants and INSTANCE.",
528
534
  )
529
535
  convert_parser.add_argument(
530
536
  "--text-width",