wunderspec 0.134.1__tar.gz → 0.136.1__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 (154) hide show
  1. wunderspec-0.136.1/CHANGELOG.md +62 -0
  2. {wunderspec-0.134.1 → wunderspec-0.136.1}/PKG-INFO +35 -25
  3. {wunderspec-0.134.1 → wunderspec-0.136.1}/README.md +34 -24
  4. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/references/from-quint-llms.md +78 -60
  5. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/references/from-tla-llms.md +24 -14
  6. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/cheatsheet.html +24 -8
  7. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/comprehensions.md +30 -0
  8. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/decorators.md +23 -5
  9. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/integers.md +43 -1
  10. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/maps.md +9 -0
  11. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/sets.md +11 -0
  12. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/state-machine.md +11 -0
  13. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/etcdraft.py +29 -1
  14. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/examples.yaml +7 -0
  15. wunderspec-0.136.1/examples/tendermint_single.py +893 -0
  16. wunderspec-0.136.1/examples/tendermint_single_indinv.py +516 -0
  17. {wunderspec-0.134.1 → wunderspec-0.136.1}/pyproject.toml +1 -1
  18. {wunderspec-0.134.1 → wunderspec-0.136.1}/tests/README.md +40 -39
  19. {wunderspec-0.134.1 → wunderspec-0.136.1}/uv.lock +1 -1
  20. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/__init__.py +5 -1
  21. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/apalache.py +94 -9
  22. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/api.py +212 -8
  23. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/action_ast.py +2 -0
  24. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/ast.py +13 -1
  25. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/cli.py +13 -0
  26. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/expr.py +37 -7
  27. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/interpreter.py +6 -3
  28. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/lang.py +206 -25
  29. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/machine.py +176 -11
  30. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/sym_context.py +8 -1
  31. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/tla.py +158 -4
  32. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/tlc.py +2 -1
  33. wunderspec-0.134.1/CHANGELOG.md +0 -27
  34. {wunderspec-0.134.1 → wunderspec-0.136.1}/.devcontainer/Dockerfile +0 -0
  35. {wunderspec-0.134.1 → wunderspec-0.136.1}/.devcontainer/devcontainer.json +0 -0
  36. {wunderspec-0.134.1 → wunderspec-0.136.1}/.devcontainer/post-create.sh +0 -0
  37. {wunderspec-0.134.1 → wunderspec-0.136.1}/.flake8 +0 -0
  38. {wunderspec-0.134.1 → wunderspec-0.136.1}/.github/workflows/build.yml +0 -0
  39. {wunderspec-0.134.1 → wunderspec-0.136.1}/.github/workflows/convert-to-tla.yml +0 -0
  40. {wunderspec-0.134.1 → wunderspec-0.136.1}/.github/workflows/lint.yml +0 -0
  41. {wunderspec-0.134.1 → wunderspec-0.136.1}/.github/workflows/publish.yml +0 -0
  42. {wunderspec-0.134.1 → wunderspec-0.136.1}/.github/workflows/run-examples.yml +0 -0
  43. {wunderspec-0.134.1 → wunderspec-0.136.1}/.gitignore +0 -0
  44. {wunderspec-0.134.1 → wunderspec-0.136.1}/CONTRIBUTING.md +0 -0
  45. {wunderspec-0.134.1 → wunderspec-0.136.1}/LICENSE +0 -0
  46. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/README.md +0 -0
  47. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-avatar-github-1024.png +0 -0
  48. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-avatar-github-256.png +0 -0
  49. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-avatar-github-512.png +0 -0
  50. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-icon-circle-dark-1024.png +0 -0
  51. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-icon-circle-dark-512.png +0 -0
  52. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-icon-circle-transparent-1024.png +0 -0
  53. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-lockup-horizontal-dark-1600.png +0 -0
  54. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-lockup-horizontal-dark-800.png +0 -0
  55. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-mark-transparent-1024.png +0 -0
  56. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-readme-header-dark-1200x480.png +0 -0
  57. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-readme-header-dark-1600x640.png +0 -0
  58. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-social-preview-1280x640.png +0 -0
  59. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/png/wunderspec-wordmark-transparent-900.png +0 -0
  60. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-avatar-github.svg +0 -0
  61. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-icon-circle-dark.svg +0 -0
  62. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-icon-circle-transparent.svg +0 -0
  63. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-lockup-horizontal-dark.svg +0 -0
  64. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-lockup-horizontal-transparent.svg +0 -0
  65. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-mark-transparent.svg +0 -0
  66. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-readme-header-dark.svg +0 -0
  67. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec-wordmark-transparent.svg +0 -0
  68. {wunderspec-0.134.1 → wunderspec-0.136.1}/assets/design/svg/wunderspec_flower.svg +0 -0
  69. {wunderspec-0.134.1 → wunderspec-0.136.1}/basedpyrightconfig.json +0 -0
  70. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/booleans.md +0 -0
  71. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/enums.md +0 -0
  72. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/flow.md +0 -0
  73. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/lists.md +0 -0
  74. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/records.md +0 -0
  75. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/strings.md +0 -0
  76. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/temporal.md +0 -0
  77. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/tuples.md +0 -0
  78. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/unions.md +0 -0
  79. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-references/wunderspec-five-minutes.md +0 -0
  80. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-stories/bobs_log.md +0 -0
  81. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-stories/bobs_log.png +0 -0
  82. {wunderspec-0.134.1 → wunderspec-0.136.1}/docs/user-stories/img/bob.png +0 -0
  83. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/bags.py +0 -0
  84. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/bakery.py +0 -0
  85. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/bakery_walk.py +0 -0
  86. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/ben_or.py +0 -0
  87. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/channel.py +0 -0
  88. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/dekker.py +0 -0
  89. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/epfd.py +0 -0
  90. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/fifo.py +0 -0
  91. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/fpaxos.py +0 -0
  92. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/kv_store.py +0 -0
  93. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/lamport_mutex.py +0 -0
  94. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/ledger.py +0 -0
  95. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/minimmit.py +0 -0
  96. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/payment.py +0 -0
  97. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/producer_consumer.py +0 -0
  98. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/readers_writers.py +0 -0
  99. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/readers_writers_walk.py +0 -0
  100. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/simple_ponzi.py +0 -0
  101. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/simple_ponzi_machine.py +0 -0
  102. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/simple_ponzi_machine_walk.py +0 -0
  103. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/simple_wal1.py +0 -0
  104. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/simple_wal2.py +0 -0
  105. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/sliding_puzzles.py +0 -0
  106. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/sloppy_counter.py +0 -0
  107. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/test_simple_ponzi_machine.py +0 -0
  108. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/tiny_workers.py +0 -0
  109. {wunderspec-0.134.1 → wunderspec-0.136.1}/examples/two_phase.py +0 -0
  110. {wunderspec-0.134.1 → wunderspec-0.136.1}/pyrightconfig.json +0 -0
  111. {wunderspec-0.134.1 → wunderspec-0.136.1}/scripts/__init__.py +0 -0
  112. {wunderspec-0.134.1 → wunderspec-0.136.1}/scripts/_spec_utils.py +0 -0
  113. {wunderspec-0.134.1 → wunderspec-0.136.1}/scripts/convert_examples_to_tla.py +0 -0
  114. {wunderspec-0.134.1 → wunderspec-0.136.1}/scripts/run_examples.py +0 -0
  115. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/__main__.py +0 -0
  116. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/_edition.py +0 -0
  117. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/__init__.py +0 -0
  118. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/list_ast.py +0 -0
  119. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/map_ast.py +0 -0
  120. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/record_ast.py +0 -0
  121. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/serialization.py +0 -0
  122. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/set_ast.py +0 -0
  123. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/sorts.py +0 -0
  124. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/temporal_ast.py +0 -0
  125. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/terms.py +0 -0
  126. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/tuple_ast.py +0 -0
  127. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/ast/union_ast.py +0 -0
  128. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/dev_debug.py +0 -0
  129. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/doc_format.py +0 -0
  130. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/errors.py +0 -0
  131. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/exec/__init__.py +0 -0
  132. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/exec/action_exec.py +0 -0
  133. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/exec/action_profile.py +0 -0
  134. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/exec/context.py +0 -0
  135. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/exec/scheduler.py +0 -0
  136. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/explain.py +0 -0
  137. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/flow.py +0 -0
  138. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/fuzzer.py +0 -0
  139. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/interpreter_sampling.py +0 -0
  140. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/interpreter_value.py +0 -0
  141. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/itf_trace.py +0 -0
  142. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/linter.py +0 -0
  143. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/model_checker.py +0 -0
  144. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/permutation.py +0 -0
  145. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/petnames.py +0 -0
  146. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/pretty.py +0 -0
  147. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/py.typed +0 -0
  148. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/quint_convert.py +0 -0
  149. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/random_walk.py +0 -0
  150. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/source_tracking.py +0 -0
  151. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/submachine.py +0 -0
  152. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/tlc_trace.py +0 -0
  153. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/trace_output.py +0 -0
  154. {wunderspec-0.134.1 → wunderspec-0.136.1}/wunderspec/uniq_names.py +0 -0
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ ## [0.136.1] -- 2026-07-04
4
+
5
+ Changes since public release v0.134.1.
6
+
7
+ - Add `--coverage NAME` to `wunderspec with-apalache`, passing the generated
8
+ TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
9
+ `check` and `simulate`.
10
+ - Allow `wunderspec with-apalache --max-steps 0` to check initial states.
11
+ - Write Apalache output to an `apalache.log` file while it runs, report the log
12
+ path, and add `--verbose` to stream the log to the terminal immediately.
13
+ - Require `default=...` for collection-form `Max` and `Min`, e.g.
14
+ `Max(s, default=0)`. The default is the reduce seed, so empty collections no
15
+ longer need a separate size guard or `CHOOSE`-based seed.
16
+ - Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
17
+ expressions in generated TLA+ while keeping reducer-based interpreter
18
+ evaluation.
19
+ - Add TLA+ labels to `wunderspec convert` output for `@invariant` and
20
+ `@example` operators, including nested predicate calls by default. Use
21
+ `@invariant(inline=True)` or `@example(inline=True)` to inline nested
22
+ predicate calls instead.
23
+ - Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
24
+ them as `\*` comments immediately before the generated operator definitions.
25
+ - Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
26
+ pass `name=` for readable TLA+ binder names while preserving internal unique
27
+ binder identities.
28
+ - Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
29
+ side is not evaluated when the antecedent is false.
30
+ - Add `Max` and `Min` builtins. They take either several integers
31
+ (`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
32
+ syntactic sugar — the multi-argument form expands to nested `Ite` and the
33
+ collection form folds with `reduce` — so they work in the interpreter, TLC,
34
+ and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
35
+ raise, mirroring `CHOOSE` over an empty set; guard with
36
+ `Ite(s.size == 0, default, Max(s))` when you need a fallback.
37
+
38
+ ## [0.134.1] -- 2026-06-26
39
+
40
+ Changes since public release v0.132.2.
41
+
42
+ - When an `@example` property is checked but no witness state is found, `run`,
43
+ `check`, `fuzz`, `replay`, `with-tlc`, and `with-apalache` now report the
44
+ outcome as `warning: No examples found …` (previously the misleading
45
+ `success: …`) and exit with the dedicated code `3`. The exit-code scheme is now
46
+ `0` = clean / no predicate, `1` = invariant violation, `2` = example found,
47
+ `3` = example not found. Invariant checks are unaffected: holding an invariant
48
+ is still `success` / exit `0`.
49
+ - Add per-action profiling to `wunderspec run` and `check`. By default they now
50
+ print a compact `fired/tried (pct%)` table, sorted by action name, counting
51
+ how many times each non-inline (`@action(inline=False)`) action was entered
52
+ (tried) and completed without violating an assumption (fired). Actions whose
53
+ fire rate is at or near 0% are highlighted in red when color is enabled.
54
+ Pass `--no-action-profiling` to disable the accumulation and the table.
55
+ - Improve `wunderspec run` trace coverage on specifications whose actions depend
56
+ on hard-to-hit guards. Add `--max-retries-per-step` (default 30): the per-trace
57
+ retry budget is `--max-retries-per-step × --max-steps`, and a trace is cut only
58
+ when it reaches `--max-steps` or exhausts that budget. The earlier
59
+ consecutive-failure cutoff, which abandoned still-progressing traces too early,
60
+ has been removed. `run` now also prints
61
+ `Trace length statistics: max=…, min=…, average=…` at the end.
62
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wunderspec
3
- Version: 0.134.1
3
+ Version: 0.136.1
4
4
  Summary: Protocol specifications as Python code
5
5
  Author: Igor Konnov, Thomas Pani
6
6
  License-File: LICENSE
@@ -103,35 +103,45 @@ commands are not included in this package.
103
103
 
104
104
  ## 4. Release Provenance
105
105
 
106
- - Release tag: `v0.134.1`
107
- - Source commit: `c9ee963d884f34db1ae946b8dec869d9c7a80c76`
106
+ - Release tag: `v0.136.1`
107
+ - Source commit: `6ef37ade3f08676708e0127d20ba14e313abbbb2`
108
108
 
109
109
  See [tests/README.md][] for the development test log captured at release time.
110
110
 
111
111
  ## 5. Latest Release Notes
112
112
 
113
- Changes since public release v0.132.2.
114
-
115
- - When an `@example` property is checked but no witness state is found, `run`,
116
- `check`, `fuzz`, `replay`, `with-tlc`, and `with-apalache` now report the
117
- outcome as `warning: No examples found …` (previously the misleading
118
- `success: …`) and exit with the dedicated code `3`. The exit-code scheme is now
119
- `0` = clean / no predicate, `1` = invariant violation, `2` = example found,
120
- `3` = example not found. Invariant checks are unaffected: holding an invariant
121
- is still `success` / exit `0`.
122
- - Add per-action profiling to `wunderspec run` and `check`. By default they now
123
- print a compact `fired/tried (pct%)` table, sorted by action name, counting
124
- how many times each non-inline (`@action(inline=False)`) action was entered
125
- (tried) and completed without violating an assumption (fired). Actions whose
126
- fire rate is at or near 0% are highlighted in red when color is enabled.
127
- Pass `--no-action-profiling` to disable the accumulation and the table.
128
- - Improve `wunderspec run` trace coverage on specifications whose actions depend
129
- on hard-to-hit guards. Add `--max-retries-per-step` (default 30): the per-trace
130
- retry budget is `--max-retries-per-step × --max-steps`, and a trace is cut only
131
- when it reaches `--max-steps` or exhausts that budget. The earlier
132
- consecutive-failure cutoff, which abandoned still-progressing traces too early,
133
- has been removed. `run` now also prints
134
- `Trace length statistics: max=…, min=…, average=…` at the end.
113
+ Changes since public release v0.134.1.
114
+
115
+ - Add `--coverage NAME` to `wunderspec with-apalache`, passing the generated
116
+ TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
117
+ `check` and `simulate`.
118
+ - Allow `wunderspec with-apalache --max-steps 0` to check initial states.
119
+ - Write Apalache output to an `apalache.log` file while it runs, report the log
120
+ path, and add `--verbose` to stream the log to the terminal immediately.
121
+ - Require `default=...` for collection-form `Max` and `Min`, e.g.
122
+ `Max(s, default=0)`. The default is the reduce seed, so empty collections no
123
+ longer need a separate size guard or `CHOOSE`-based seed.
124
+ - Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
125
+ expressions in generated TLA+ while keeping reducer-based interpreter
126
+ evaluation.
127
+ - Add TLA+ labels to `wunderspec convert` output for `@invariant` and
128
+ `@example` operators, including nested predicate calls by default. Use
129
+ `@invariant(inline=True)` or `@example(inline=True)` to inline nested
130
+ predicate calls instead.
131
+ - Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
132
+ them as `\*` comments immediately before the generated operator definitions.
133
+ - Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
134
+ pass `name=` for readable TLA+ binder names while preserving internal unique
135
+ binder identities.
136
+ - Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
137
+ side is not evaluated when the antecedent is false.
138
+ - Add `Max` and `Min` builtins. They take either several integers
139
+ (`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
140
+ syntactic sugar — the multi-argument form expands to nested `Ite` and the
141
+ collection form folds with `reduce` — so they work in the interpreter, TLC,
142
+ and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
143
+ raise, mirroring `CHOOSE` over an empty set; guard with
144
+ `Ite(s.size == 0, default, Max(s))` when you need a fallback.
135
145
 
136
146
  ## 6. License
137
147
 
@@ -87,35 +87,45 @@ commands are not included in this package.
87
87
 
88
88
  ## 4. Release Provenance
89
89
 
90
- - Release tag: `v0.134.1`
91
- - Source commit: `c9ee963d884f34db1ae946b8dec869d9c7a80c76`
90
+ - Release tag: `v0.136.1`
91
+ - Source commit: `6ef37ade3f08676708e0127d20ba14e313abbbb2`
92
92
 
93
93
  See [tests/README.md][] for the development test log captured at release time.
94
94
 
95
95
  ## 5. Latest Release Notes
96
96
 
97
- Changes since public release v0.132.2.
98
-
99
- - When an `@example` property is checked but no witness state is found, `run`,
100
- `check`, `fuzz`, `replay`, `with-tlc`, and `with-apalache` now report the
101
- outcome as `warning: No examples found …` (previously the misleading
102
- `success: …`) and exit with the dedicated code `3`. The exit-code scheme is now
103
- `0` = clean / no predicate, `1` = invariant violation, `2` = example found,
104
- `3` = example not found. Invariant checks are unaffected: holding an invariant
105
- is still `success` / exit `0`.
106
- - Add per-action profiling to `wunderspec run` and `check`. By default they now
107
- print a compact `fired/tried (pct%)` table, sorted by action name, counting
108
- how many times each non-inline (`@action(inline=False)`) action was entered
109
- (tried) and completed without violating an assumption (fired). Actions whose
110
- fire rate is at or near 0% are highlighted in red when color is enabled.
111
- Pass `--no-action-profiling` to disable the accumulation and the table.
112
- - Improve `wunderspec run` trace coverage on specifications whose actions depend
113
- on hard-to-hit guards. Add `--max-retries-per-step` (default 30): the per-trace
114
- retry budget is `--max-retries-per-step × --max-steps`, and a trace is cut only
115
- when it reaches `--max-steps` or exhausts that budget. The earlier
116
- consecutive-failure cutoff, which abandoned still-progressing traces too early,
117
- has been removed. `run` now also prints
118
- `Trace length statistics: max=…, min=…, average=…` at the end.
97
+ Changes since public release v0.134.1.
98
+
99
+ - Add `--coverage NAME` to `wunderspec with-apalache`, passing the generated
100
+ TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
101
+ `check` and `simulate`.
102
+ - Allow `wunderspec with-apalache --max-steps 0` to check initial states.
103
+ - Write Apalache output to an `apalache.log` file while it runs, report the log
104
+ path, and add `--verbose` to stream the log to the terminal immediately.
105
+ - Require `default=...` for collection-form `Max` and `Min`, e.g.
106
+ `Max(s, default=0)`. The default is the reduce seed, so empty collections no
107
+ longer need a separate size guard or `CHOOSE`-based seed.
108
+ - Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
109
+ expressions in generated TLA+ while keeping reducer-based interpreter
110
+ evaluation.
111
+ - Add TLA+ labels to `wunderspec convert` output for `@invariant` and
112
+ `@example` operators, including nested predicate calls by default. Use
113
+ `@invariant(inline=True)` or `@example(inline=True)` to inline nested
114
+ predicate calls instead.
115
+ - Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
116
+ them as `\*` comments immediately before the generated operator definitions.
117
+ - Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
118
+ pass `name=` for readable TLA+ binder names while preserving internal unique
119
+ binder identities.
120
+ - Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
121
+ side is not evaluated when the antecedent is false.
122
+ - Add `Max` and `Min` builtins. They take either several integers
123
+ (`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
124
+ syntactic sugar — the multi-argument form expands to nested `Ite` and the
125
+ collection form folds with `reduce` — so they work in the interpreter, TLC,
126
+ and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
127
+ raise, mirroring `CHOOSE` over an empty set; guard with
128
+ `Ite(s.size == 0, default, Max(s))` when you need a fallback.
119
129
 
120
130
  ## 6. License
121
131
 
@@ -12,7 +12,18 @@ This guide uses the current shorthand annotations: `Param[T]`,
12
12
  forms are still accepted by Wunderspec, but new translations should prefer
13
13
  these shorthands.
14
14
 
15
- ## 1. Translation mindset
15
+ ## 1. Preferred workflow
16
+
17
+ Prefer using the Quint converter first:
18
+
19
+ ```sh
20
+ wunderspec convert --from=spec.qnt --to=spec.py
21
+ ```
22
+
23
+ Use the manual rules below to review, refine, and idiomatize the generated
24
+ Wunderspec model, or when the converter does not yet cover a Quint construct.
25
+
26
+ ## 2. Translation mindset
16
27
 
17
28
  A good translation keeps the same:
18
29
 
@@ -28,7 +39,7 @@ A good translation may still change:
28
39
  - how tagged unions (`Option`, `Result`, ...) are represented (use `@union`)
29
40
  - embedded module imports (inline definitions from imported `.qnt` files)
30
41
 
31
- ## 2. Minimal Wunderspec skeleton
42
+ ## 3. Minimal Wunderspec skeleton
32
43
 
33
44
  ```python
34
45
  from wunderspec import *
@@ -52,7 +63,7 @@ def step(c: Context[SpecState]):
52
63
  s.x = s.x + Val(1)
53
64
  ```
54
65
 
55
- ## 3. Quint module elements
66
+ ## 4. Quint module elements
56
67
 
57
68
  | Quint | Wunderspec |
58
69
  |---|---|
@@ -77,7 +88,7 @@ Rule of thumb:
77
88
  - Quint type aliases (`type ViewNumber = int`) become Python type annotations
78
89
  or comments — no runtime objects needed.
79
90
 
80
- ### 3.1 Handling imported Quint modules
91
+ ### 4.1 Handling imported Quint modules
81
92
 
82
93
  Quint specs often `import` definitions from other `.qnt` files. The recommended
83
94
  approach is to **inline** all imported definitions at the top of the Python
@@ -101,7 +112,7 @@ class Vote:
101
112
  This avoids creating additional Python modules for definitions that are
102
113
  conceptually part of a single flat specification.
103
114
 
104
- ### 3.2 Fixing constants with `@instance`
115
+ ### 4.2 Fixing constants with `@instance`
105
116
 
106
117
  Quint `const` declarations become `Param[...]` fields in the `@state` class.
107
118
  To make a concrete parameter assignment reusable, annotate a no-argument
@@ -119,9 +130,9 @@ Rules of thumb:
119
130
  is the only way to supply a state prototype on the CLI.
120
131
  - Name each factory after the scenario it represents (`n5_f1`, `two_actors`).
121
132
 
122
- ## 4. Expression primitives
133
+ ## 5. Expression primitives
123
134
 
124
- ### 4.1 Literals and constructors
135
+ ### 5.1 Literals and constructors
125
136
 
126
137
  | Quint | Wunderspec |
127
138
  |---|---|
@@ -134,7 +145,7 @@ Rules of thumb:
134
145
  | `[k -> v]` (map literal) | `Map((k, v))` |
135
146
  | `List()` (empty sequence) | `List(T)` |
136
147
 
137
- ### 4.2 Boolean and arithmetic operators
148
+ ### 5.2 Boolean and arithmetic operators
138
149
 
139
150
  | Quint | Wunderspec |
140
151
  |---|---|
@@ -146,7 +157,7 @@ Rules of thumb:
146
157
  | `<`, `<=`, `>`, `>=` | same operators |
147
158
  | `+`, `-`, `*`, `/`, `%` | same operators (`/` is integer division) |
148
159
 
149
- ### 4.3 Sets
160
+ ### 5.3 Sets
150
161
 
151
162
  | Quint | Wunderspec |
152
163
  |---|---|
@@ -175,7 +186,7 @@ def is_quorum(subset, conf):
175
186
  return And(subset.issubset(conf), subset.size * 2 > conf.size)
176
187
  ```
177
188
 
178
- ### 4.4 Maps (Quint records with `->`)
189
+ ### 5.4 Maps (Quint records with `->`)
179
190
 
180
191
  | Quint | Wunderspec |
181
192
  |---|---|
@@ -192,7 +203,7 @@ def is_quorum(subset, conf):
192
203
  inserts or updates. For a state map whose keyed assignment may add a new key,
193
204
  declare it `StateVar[dict[K, V], UPSERT]` (`UPSERT` from `wunderspec.machine`).
194
205
 
195
- ### 4.5 Sequences/lists
206
+ ### 5.5 Sequences/lists
196
207
 
197
208
  | Quint | Wunderspec |
198
209
  |---|---|
@@ -204,7 +215,7 @@ declare it `StateVar[dict[K, V], UPSERT]` (`UPSERT` from `wunderspec.machine`).
204
215
  | `l.slice(i, j)` | `l[i:j]` |
205
216
  | `l.filter(x => P)` | `l.filter(lambda x: P)` |
206
217
 
207
- ### 4.6 Records and `@record` types
218
+ ### 5.6 Records and `@record` types
208
219
 
209
220
  Quint `type` definitions with named fields map directly to Wunderspec `@record`
210
221
  classes. Field access uses the same dot notation.
@@ -232,7 +243,7 @@ v = Vote(view=view, block=block, sig=sig, kind=kind)
232
243
  v.view # field access
233
244
  ```
234
245
 
235
- ### 4.7 Record updates (Quint spread syntax)
246
+ ### 5.7 Record updates (Quint spread syntax)
236
247
 
237
248
  When the spread produces a **value** (a record to send, return, or feed into a
238
249
  larger expression), translate `{ ...rec, field: val }` with `.replace()`:
@@ -303,9 +314,11 @@ is expected to exist already. When translating a Quint update that may insert a
303
314
  new map entry, declare the field with the `UPSERT` marker:
304
315
  `StateVar[dict[K, V], UPSERT]`.
305
316
 
306
- ### 4.8 Conditional expressions (`if/else`)
317
+ ### 5.8 Conditional expressions (`if/else`)
307
318
 
308
- Quint `if`/`else` in pure expressions becomes `Ite(cond, then_expr, else_expr)`:
319
+ Quint `if`/`else` in pure expressions becomes
320
+ `then_expr.if_(cond).else_(else_expr)`. Prefer this method form over
321
+ `Ite(cond, then_expr, else_expr)`.
309
322
 
310
323
  Quint:
311
324
  ```quint
@@ -314,21 +327,21 @@ if (view < new_view) new_view else view
314
327
 
315
328
  Wunderspec:
316
329
  ```python
317
- Ite(self_rec.view < new_view, new_view, self_rec.view)
330
+ new_view.if_(self_rec.view < new_view).else_(self_rec.view)
318
331
  ```
319
332
 
320
- Use `Ite` for conditional assignments inside actions when both branches modify
333
+ Use a conditional value for assignments inside actions when both branches modify
321
334
  the same variable but you do not want to split the action:
322
335
 
323
336
  ```python
324
- s.store_certificate = Ite(
325
- is_new_cert,
326
- s.store_certificate.replace(id, s.store_certificate[id] | Set(cert)),
327
- s.store_certificate,
337
+ s.store_certificate = (
338
+ s.store_certificate.replace(id, s.store_certificate[id] | Set(cert))
339
+ .if_(is_new_cert)
340
+ .else_(s.store_certificate)
328
341
  )
329
342
  ```
330
343
 
331
- ### 4.9 Quantifiers in invariants
344
+ ### 5.9 Quantifiers in invariants
332
345
 
333
346
  For invariants that universally quantify over multiple sets (e.g., all pairs of
334
347
  replicas and views), use the `Forall` generator form:
@@ -343,7 +356,7 @@ This is the direct analogue of Quint's nested `forall`:
343
356
  CORRECT.forall(id => VIEWS.forall(v => check(id, v)))
344
357
  ```
345
358
 
346
- ### 4.10 `let ... in` and shared subexpressions
359
+ ### 5.10 `let ... in` and shared subexpressions
347
360
 
348
361
  Quint `val a = e ...` / `let a = e { ... }` becomes an ordinary Python local:
349
362
  `a = e`. If `e` is large and reused and you want it emitted **once** in the
@@ -353,7 +366,7 @@ compiled spec, bind it with `c.cache`:
353
366
  quorum = c.cache(votes & members, "quorum")
354
367
  ```
355
368
 
356
- ### 4.11 Multisets / bags
369
+ ### 5.11 Multisets / bags
357
370
 
358
371
  Quint state that is conceptually a *multiset* (counts matter — messages can be
359
372
  duplicated or removed one copy at a time) maps to the user-space `Bag` ADT from
@@ -371,12 +384,12 @@ Bag(s.messages)[msg] # how many copies
371
384
  For heterogeneous messages, wrap an envelope record around a `@union` payload
372
385
  (see §5.7 and `examples/etcdraft.py`).
373
386
 
374
- ## 5. Action translation rules
387
+ ## 6. Action translation rules
375
388
 
376
389
  Quint actions describe guarded assignments. In Wunderspec, write guards with
377
390
  `c.assume(...)` and assignments on `c.state`.
378
391
 
379
- ### 5.1 Guards (`all { guard, ... }`)
392
+ ### 6.1 Guards (`all { guard, ... }`)
380
393
 
381
394
  Quint:
382
395
  ```quint
@@ -398,7 +411,7 @@ def proposer_step(c: Context[MinimmitState], id: Expr, new_block: Expr):
398
411
  ...
399
412
  ```
400
413
 
401
- ### 5.2 Primed assignments (`x' = e`)
414
+ ### 6.2 Primed assignments (`x' = e`)
402
415
 
403
416
  Quint uses `x' = e` for state updates inside actions. In Wunderspec, assign
404
417
  directly on `s`:
@@ -413,11 +426,11 @@ Wunderspec:
413
426
  s.sent_vote = s.sent_vote | Set(notarize_vote)
414
427
  ```
415
428
 
416
- ### 5.3 `UNCHANGED` (implicit)
429
+ ### 6.3 `UNCHANGED` (implicit)
417
430
 
418
431
  Variables you do not assign remain unchanged. No explicit code is needed.
419
432
 
420
- ### 5.4 Nested map/record updates
433
+ ### 6.4 Nested map/record updates
421
434
 
422
435
  **Prefer direct assignment on `s`** for state updates — map entries, nested
423
436
  paths, and record fields all assign directly. This is the idiomatic translation
@@ -453,11 +466,11 @@ s.store_vote[id] = s.store_vote[id] | votes
453
466
  ```
454
467
 
455
468
  `.replace(...)`/`.edit()` are for building a **value not bound to a state path** —
456
- a record to put in a message, a helper's return value, or a branch of an
457
- `Ite(...)`. When the target *is* a state path, write `s.x[k] = v`, not
469
+ a record to put in a message, a helper's return value, or a branch of a
470
+ conditional value. When the target *is* a state path, write `s.x[k] = v`, not
458
471
  `s.x = s.x.replace(k, v)`.
459
472
 
460
- ### 5.5 Disjunction (`any { ... }`) in actions
473
+ ### 6.5 Disjunction (`any { ... }`) in actions
461
474
 
462
475
  Quint's `any { A, B, C }` becomes `c.alternatives()`:
463
476
 
@@ -485,7 +498,7 @@ def step(c: Context[MyState]):
485
498
 
486
499
  Alternative labels are stable strings; they appear in replay schedules.
487
500
 
488
- ### 5.6 Existential choice (`nondet x = S.oneOf()`)
501
+ ### 6.6 Existential choice (`nondet x = S.oneOf()`)
489
502
 
490
503
  Quint:
491
504
  ```quint
@@ -552,7 +565,7 @@ with (
552
565
  s.sent_vote = s.sent_vote | byz_senders.map(lambda _s: byz_vote)
553
566
  ```
554
567
 
555
- ### 5.7 The `Option` type — translate with `@union`
568
+ ### 6.7 The `Option` type — translate with `@union`
556
569
 
557
570
  Quint's `Option[T]` (variants `Some(payload)` and `None`) maps directly to a
558
571
  Wunderspec `@union` type. Define it once near the top of the file, after the
@@ -579,8 +592,9 @@ OptionCertificate.Some(cert)
579
592
  OptionCertificate.None_()
580
593
  ```
581
594
 
582
- **Building an option value in a pure function** — use `Ite` (both branches
583
- have the same `UnionSort`, so the types match):
595
+ **Building an option value in a pure function** — use
596
+ `then_value.if_(condition).else_(else_value)`. Both branches have the same
597
+ `UnionSort`, so the types match:
584
598
 
585
599
  Quint:
586
600
  ```quint
@@ -603,13 +617,17 @@ def create_notarization(
603
617
  lambda v: (v.view == view) & (v.kind == Val(NOTARIZE_KIND)) & (v.block == block)
604
618
  )
605
619
  votes_count = similar_votes.size
606
- cert_kind = Ite(votes_count >= L_quorum(s), Val(FINALIZATION_KIND), Val(NOTARIZATION_KIND))
607
- cert = mk_certificate(view, block, similar_votes.map(lambda v: v.sig), id, cert_kind)
608
- return Ite( # type: ignore[return-value]
609
- votes_count >= M_quorum(s),
610
- OptionCertificate.Some(cert),
611
- OptionCertificate.None_(),
620
+ cert_kind = (
621
+ Val(FINALIZATION_KIND)
622
+ .if_(votes_count >= L_quorum(s))
623
+ .else_(NOTARIZATION_KIND)
624
+ )
625
+ cert = mk_certificate(
626
+ view, block, similar_votes.map(lambda v: v.sig), id, cert_kind
612
627
  )
628
+ return OptionCertificate.Some(cert).if_(votes_count >= M_quorum(s)).else_(
629
+ OptionCertificate.None_()
630
+ ) # type: ignore[return-value]
613
631
  ```
614
632
 
615
633
  **Pattern matching in a pure expression** — use `.match()`:
@@ -663,24 +681,24 @@ sort.
663
681
  block — even if one branch is a no-op (`pass`). Omitting a branch silently
664
682
  makes the split one-sided.
665
683
 
666
- ### 5.8 Conditional state changes inside a single action
684
+ ### 6.8 Conditional state changes inside a single action
667
685
 
668
686
  When an action conditionally modifies a variable (no split desired), use
669
- `Ite()` directly as the assigned value:
687
+ `then_value.if_(condition).else_(else_value)` as the assigned value:
670
688
 
671
689
  ```python
672
- s.sent_vote = Ite(
673
- should_send_notarize_vote,
674
- s.sent_vote | Set(mk_notarize(cert.view, id, cert.block)),
675
- s.sent_vote,
690
+ s.sent_vote = (
691
+ (s.sent_vote | Set(mk_notarize(cert.view, id, cert.block)))
692
+ .if_(should_send_notarize_vote)
693
+ .else_(s.sent_vote)
676
694
  )
677
695
  ```
678
696
 
679
697
  Use `c.split()` when the two branches have substantially different update
680
- patterns; use `Ite()` when you can express both outcomes as a single
681
- expression.
698
+ patterns; use a conditional value when you can express both outcomes as a
699
+ single expression.
682
700
 
683
- ## 6. Invariants and properties
701
+ ## 7. Invariants and properties
684
702
 
685
703
  Keep invariants as pure functions over state decorated with `@invariant`:
686
704
 
@@ -690,7 +708,7 @@ def agreement(s: MinimmitState) -> BoolExpr:
690
708
  def check(id1: Expr, id2: Expr) -> BoolExpr:
691
709
  b1 = s.ghost_committed_blocks[id1]
692
710
  b2 = s.ghost_committed_blocks[id2]
693
- n = b1.size.min(b2.size)
711
+ n = Min(b1.size, b2.size)
694
712
  return b1[:n] == b2[:n]
695
713
  return Forall(check(id1, id2) for id1 in s.CORRECT for id2 in s.CORRECT)
696
714
  ```
@@ -719,7 +737,7 @@ If a Quint spec is unbounded, add `Param` caps (e.g. `MaxView`) and guard the
719
737
  space-growing actions with `c.assume(s.view[id] < s.MaxView)` for bounded model
720
738
  checking; document them as the only deviations from the source.
721
739
 
722
- ## 7. Helper function conventions
740
+ ## 8. Helper function conventions
723
741
 
724
742
  - Put `s: MyState` as the **first** argument of every helper that references state.
725
743
  - Pure helpers that do not reference state receive only their own arguments.
@@ -741,7 +759,7 @@ def is_view_notarized_votes(
741
759
  .size >= M_quorum(s)
742
760
  ```
743
761
 
744
- ## 8. Naming and style conventions
762
+ ## 9. Naming and style conventions
745
763
 
746
764
  - Quint `camelCase` identifiers → Python `snake_case`.
747
765
  - Quint `SCREAMING_SNAKE` constants → Python module-level constants (no change needed).
@@ -752,7 +770,7 @@ def is_view_notarized_votes(
752
770
  - Inline imported Quint modules at the top of the Python file, separated by
753
771
  banner comments.
754
772
 
755
- ## 9. Common pitfalls
773
+ ## 10. Common pitfalls
756
774
 
757
775
  - Do not use Python `and`/`or`/`not` with Wunderspec expressions; use `And`,
758
776
  `Or`, `~`.
@@ -766,8 +784,8 @@ def is_view_notarized_votes(
766
784
  - Prefer **direct assignment** for state updates: `s.m[k] = v`,
767
785
  `s.rec.field = v`, `s.m[i][j] = v`. Reserve `.replace`/`.insert`/`.edit` for
768
786
  building a value not bound to a state path (a message, a return value, an
769
- `Ite` branch). Translate `map.set(k, v)` writing to state as `s.m[k] = v`, not
770
- `s.m = s.m.replace(k, v)`.
787
+ conditional branch). Translate `map.set(k, v)` writing to state as
788
+ `s.m[k] = v`, not `s.m = s.m.replace(k, v)`.
771
789
  - `m.replace(k, v)` is replace-only (key must exist); use `m.insert(k, v)` to add
772
790
  a key, and `StateVar[dict[K, V], UPSERT]` for state maps that grow.
773
791
  - `expr.edit()` returns a builder whose updated value is `upd.result` — for a
@@ -776,7 +794,7 @@ def is_view_notarized_votes(
776
794
  the field as `StateVar[dict[K, V], UPSERT]` for insert-or-update behavior.
777
795
  - Use `with s.editing() as upd:` only when several updates must read the
778
796
  pre-update state atomically.
779
- - `Ite` takes three arguments: `Ite(condition, then_value, else_value)`.
797
+ - Prefer `then_value.if_(condition).else_(else_value)` for conditional values.
780
798
  - Quint's `nondet x = S.oneOf()` picks one element; in Wunderspec use
781
799
  `c.one_of(S, "x")` — do not use `S.exists(...)` inside an action for this.
782
800
  - Quint's `nondet senders = S.powerset().oneOf()` picks a *subset*; use
@@ -786,7 +804,7 @@ def is_view_notarized_votes(
786
804
  - For parameterized specs (e.g., `CORRECT`, `REPLICA_KEYS`), define an
787
805
  `@instance` factory and run it with `--instance NAME`.
788
806
 
789
- ## 10. Minimmit mapping example
807
+ ## 11. Minimmit mapping example
790
808
 
791
809
  ### `const`/`var` → `@state`
792
810
 
@@ -872,7 +890,7 @@ def step(c: Context[MinimmitState]):
872
890
  byzantine_replica_step(c)
873
891
  ```
874
892
 
875
- ## 11. Further reading and open TODOs
893
+ ## 12. Further reading and open TODOs
876
894
 
877
895
  Worked translations: `examples/minimmit.py` (this guide's running example),
878
896
  `examples/etcdraft.py` (union messages + multiset bags + sequences), and