wunderspec 0.134.1__tar.gz → 0.136.4__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.
- wunderspec-0.136.4/CHANGELOG.md +68 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/PKG-INFO +6 -25
- {wunderspec-0.134.1 → wunderspec-0.136.4}/README.md +5 -24
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/references/from-quint-llms.md +78 -60
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/references/from-tla-llms.md +24 -14
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/cheatsheet.html +24 -8
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/comprehensions.md +30 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/decorators.md +23 -5
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/integers.md +43 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/maps.md +9 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/sets.md +11 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/state-machine.md +11 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/etcdraft.py +29 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/examples.yaml +7 -0
- wunderspec-0.136.4/examples/tendermint_single.py +859 -0
- wunderspec-0.136.4/examples/tendermint_single_indinv.py +807 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/pyproject.toml +1 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/tests/README.md +41 -40
- {wunderspec-0.134.1 → wunderspec-0.136.4}/uv.lock +1 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/__init__.py +5 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/apalache.py +97 -11
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/api.py +284 -10
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/action_ast.py +2 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/ast.py +13 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/cli.py +13 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/expr.py +37 -7
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/interpreter.py +6 -3
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/lang.py +206 -25
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/machine.py +176 -11
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/sym_context.py +8 -1
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/tla.py +158 -4
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/tlc.py +5 -3
- wunderspec-0.134.1/CHANGELOG.md +0 -27
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.devcontainer/Dockerfile +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.devcontainer/devcontainer.json +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.devcontainer/post-create.sh +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.flake8 +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.github/workflows/build.yml +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.github/workflows/convert-to-tla.yml +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.github/workflows/lint.yml +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.github/workflows/publish.yml +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.github/workflows/run-examples.yml +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/.gitignore +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/CONTRIBUTING.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/LICENSE +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/README.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-1024.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-256.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-512.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-dark-1024.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-dark-512.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-transparent-1024.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-lockup-horizontal-dark-1600.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-lockup-horizontal-dark-800.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-mark-transparent-1024.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-readme-header-dark-1200x480.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-readme-header-dark-1600x640.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-social-preview-1280x640.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-wordmark-transparent-900.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-avatar-github.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-icon-circle-dark.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-icon-circle-transparent.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-lockup-horizontal-dark.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-lockup-horizontal-transparent.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-mark-transparent.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-readme-header-dark.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-wordmark-transparent.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec_flower.svg +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/basedpyrightconfig.json +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/booleans.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/enums.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/flow.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/lists.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/records.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/strings.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/temporal.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/tuples.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/unions.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-references/wunderspec-five-minutes.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-stories/bobs_log.md +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-stories/bobs_log.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/docs/user-stories/img/bob.png +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/bags.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/bakery.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/bakery_walk.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/ben_or.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/channel.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/dekker.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/epfd.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/fifo.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/fpaxos.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/kv_store.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/lamport_mutex.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/ledger.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/minimmit.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/payment.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/producer_consumer.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/readers_writers.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/readers_writers_walk.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/simple_ponzi.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/simple_ponzi_machine.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/simple_ponzi_machine_walk.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/simple_wal1.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/simple_wal2.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/sliding_puzzles.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/sloppy_counter.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/test_simple_ponzi_machine.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/tiny_workers.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/examples/two_phase.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/pyrightconfig.json +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/scripts/__init__.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/scripts/_spec_utils.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/scripts/convert_examples_to_tla.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/scripts/run_examples.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/__main__.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/_edition.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/__init__.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/list_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/map_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/record_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/serialization.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/set_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/sorts.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/temporal_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/terms.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/tuple_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/ast/union_ast.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/dev_debug.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/doc_format.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/errors.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/exec/__init__.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/exec/action_exec.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/exec/action_profile.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/exec/context.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/exec/scheduler.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/explain.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/flow.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/fuzzer.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/interpreter_sampling.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/interpreter_value.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/itf_trace.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/linter.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/model_checker.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/permutation.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/petnames.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/pretty.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/py.typed +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/quint_convert.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/random_walk.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/source_tracking.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/submachine.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/tlc_trace.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/trace_output.py +0 -0
- {wunderspec-0.134.1 → wunderspec-0.136.4}/wunderspec/uniq_names.py +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.136.4] -- 2026-07-06
|
|
4
|
+
|
|
5
|
+
Changes since public release v0.136.1.
|
|
6
|
+
|
|
7
|
+
- Add an inductive invariant in `examples/tendermint_single_indinv.py`.
|
|
8
|
+
|
|
9
|
+
## [0.136.1] -- 2026-07-04
|
|
10
|
+
|
|
11
|
+
Changes since public release v0.134.1.
|
|
12
|
+
|
|
13
|
+
- Add `--coverage NAME` to `wunderspec with-apalache`, passing the generated
|
|
14
|
+
TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
|
|
15
|
+
`check` and `simulate`.
|
|
16
|
+
- Allow `wunderspec with-apalache --max-steps 0` to check initial states.
|
|
17
|
+
- Write Apalache output to an `apalache.log` file while it runs, report the log
|
|
18
|
+
path, and add `--verbose` to stream the log to the terminal immediately.
|
|
19
|
+
- Require `default=...` for collection-form `Max` and `Min`, e.g.
|
|
20
|
+
`Max(s, default=0)`. The default is the reduce seed, so empty collections no
|
|
21
|
+
longer need a separate size guard or `CHOOSE`-based seed.
|
|
22
|
+
- Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
|
|
23
|
+
expressions in generated TLA+ while keeping reducer-based interpreter
|
|
24
|
+
evaluation.
|
|
25
|
+
- Add TLA+ labels to `wunderspec convert` output for `@invariant` and
|
|
26
|
+
`@example` operators, including nested predicate calls by default. Use
|
|
27
|
+
`@invariant(inline=True)` or `@example(inline=True)` to inline nested
|
|
28
|
+
predicate calls instead.
|
|
29
|
+
- Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
|
|
30
|
+
them as `\*` comments immediately before the generated operator definitions.
|
|
31
|
+
- Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
|
|
32
|
+
pass `name=` for readable TLA+ binder names while preserving internal unique
|
|
33
|
+
binder identities.
|
|
34
|
+
- Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
|
|
35
|
+
side is not evaluated when the antecedent is false.
|
|
36
|
+
- Add `Max` and `Min` builtins. They take either several integers
|
|
37
|
+
(`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
|
|
38
|
+
syntactic sugar — the multi-argument form expands to nested `Ite` and the
|
|
39
|
+
collection form folds with `reduce` — so they work in the interpreter, TLC,
|
|
40
|
+
and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
|
|
41
|
+
raise, mirroring `CHOOSE` over an empty set; guard with
|
|
42
|
+
`Ite(s.size == 0, default, Max(s))` when you need a fallback.
|
|
43
|
+
|
|
44
|
+
## [0.134.1] -- 2026-06-26
|
|
45
|
+
|
|
46
|
+
Changes since public release v0.132.2.
|
|
47
|
+
|
|
48
|
+
- When an `@example` property is checked but no witness state is found, `run`,
|
|
49
|
+
`check`, `fuzz`, `replay`, `with-tlc`, and `with-apalache` now report the
|
|
50
|
+
outcome as `warning: No examples found …` (previously the misleading
|
|
51
|
+
`success: …`) and exit with the dedicated code `3`. The exit-code scheme is now
|
|
52
|
+
`0` = clean / no predicate, `1` = invariant violation, `2` = example found,
|
|
53
|
+
`3` = example not found. Invariant checks are unaffected: holding an invariant
|
|
54
|
+
is still `success` / exit `0`.
|
|
55
|
+
- Add per-action profiling to `wunderspec run` and `check`. By default they now
|
|
56
|
+
print a compact `fired/tried (pct%)` table, sorted by action name, counting
|
|
57
|
+
how many times each non-inline (`@action(inline=False)`) action was entered
|
|
58
|
+
(tried) and completed without violating an assumption (fired). Actions whose
|
|
59
|
+
fire rate is at or near 0% are highlighted in red when color is enabled.
|
|
60
|
+
Pass `--no-action-profiling` to disable the accumulation and the table.
|
|
61
|
+
- Improve `wunderspec run` trace coverage on specifications whose actions depend
|
|
62
|
+
on hard-to-hit guards. Add `--max-retries-per-step` (default 30): the per-trace
|
|
63
|
+
retry budget is `--max-retries-per-step × --max-steps`, and a trace is cut only
|
|
64
|
+
when it reaches `--max-steps` or exhausts that budget. The earlier
|
|
65
|
+
consecutive-failure cutoff, which abandoned still-progressing traces too early,
|
|
66
|
+
has been removed. `run` now also prints
|
|
67
|
+
`Trace length statistics: max=…, min=…, average=…` at the end.
|
|
68
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wunderspec
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.136.4
|
|
4
4
|
Summary: Protocol specifications as Python code
|
|
5
5
|
Author: Igor Konnov, Thomas Pani
|
|
6
6
|
License-File: LICENSE
|
|
@@ -103,35 +103,16 @@ commands are not included in this package.
|
|
|
103
103
|
|
|
104
104
|
## 4. Release Provenance
|
|
105
105
|
|
|
106
|
-
- Release tag: `v0.
|
|
107
|
-
- Source commit: `
|
|
106
|
+
- Release tag: `v0.136.4`
|
|
107
|
+
- Source commit: `7beedd2736b068f1c371f59fd8ed619a1d9e75a5`
|
|
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.
|
|
114
|
-
|
|
115
|
-
-
|
|
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.136.1.
|
|
114
|
+
|
|
115
|
+
- Add an inductive invariant in `examples/tendermint_single_indinv.py`.
|
|
135
116
|
|
|
136
117
|
## 6. License
|
|
137
118
|
|
|
@@ -87,35 +87,16 @@ commands are not included in this package.
|
|
|
87
87
|
|
|
88
88
|
## 4. Release Provenance
|
|
89
89
|
|
|
90
|
-
- Release tag: `v0.
|
|
91
|
-
- Source commit: `
|
|
90
|
+
- Release tag: `v0.136.4`
|
|
91
|
+
- Source commit: `7beedd2736b068f1c371f59fd8ed619a1d9e75a5`
|
|
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.
|
|
98
|
-
|
|
99
|
-
-
|
|
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.136.1.
|
|
98
|
+
|
|
99
|
+
- Add an inductive invariant in `examples/tendermint_single_indinv.py`.
|
|
119
100
|
|
|
120
101
|
## 6. License
|
|
121
102
|
|
|
@@ -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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
##
|
|
133
|
+
## 5. Expression primitives
|
|
123
134
|
|
|
124
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
317
|
+
### 5.8 Conditional expressions (`if/else`)
|
|
307
318
|
|
|
308
|
-
Quint `if`/`else` in pure expressions becomes
|
|
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
|
-
|
|
330
|
+
new_view.if_(self_rec.view < new_view).else_(self_rec.view)
|
|
318
331
|
```
|
|
319
332
|
|
|
320
|
-
Use
|
|
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 =
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
429
|
+
### 6.3 `UNCHANGED` (implicit)
|
|
417
430
|
|
|
418
431
|
Variables you do not assign remain unchanged. No explicit code is needed.
|
|
419
432
|
|
|
420
|
-
###
|
|
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
|
|
457
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
|
583
|
-
|
|
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 =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
###
|
|
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
|
-
`
|
|
687
|
+
`then_value.if_(condition).else_(else_value)` as the assigned value:
|
|
670
688
|
|
|
671
689
|
```python
|
|
672
|
-
s.sent_vote =
|
|
673
|
-
|
|
674
|
-
|
|
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
|
|
681
|
-
expression.
|
|
698
|
+
patterns; use a conditional value when you can express both outcomes as a
|
|
699
|
+
single expression.
|
|
682
700
|
|
|
683
|
-
##
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
@@ -332,14 +332,16 @@ key. See §5.7 for dispatching on the tag inside an action, and
|
|
|
332
332
|
|
|
333
333
|
| TLA+ | Wunderspec |
|
|
334
334
|
|---|---|
|
|
335
|
-
| `IF
|
|
335
|
+
| `IF a THEN b ELSE c` | `b.if_(a).else_(c)` |
|
|
336
336
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
Prefer the method form `b.if_(a).else_(c)` over `Ite(a, b, c)`. It keeps the
|
|
338
|
+
then-branch visually first, like the TLA+ source, and auto-coerces raw literal
|
|
339
|
+
branches. Both branches must have the same sort. Use a conditional *value* to
|
|
340
|
+
fold a one-variable `EXCEPT`-with-`IF` into a single assignment instead of
|
|
341
|
+
splitting the action:
|
|
340
342
|
|
|
341
343
|
```python
|
|
342
|
-
s.votes_granted[i] =
|
|
344
|
+
s.votes_granted[i] = (s.votes_granted[i] | Set(j)).if_(granted).else_(s.votes_granted[i])
|
|
343
345
|
```
|
|
344
346
|
|
|
345
347
|
### 4.9 Powersets, function sets, and quorums
|
|
@@ -397,18 +399,26 @@ s.messages = Bag(s.messages).remove_one(m).as_map # Discard(m)
|
|
|
397
399
|
`Ready(i)` action flushing one server's pending bag into the network — and
|
|
398
400
|
duplicate/drop fault actions that rely on the multiplicity count.
|
|
399
401
|
|
|
400
|
-
### 4.11 `Min`/`Max`
|
|
402
|
+
### 4.11 `Min`/`Max`
|
|
401
403
|
|
|
402
|
-
|
|
403
|
-
integers
|
|
404
|
+
Use the built-in `Max`/`Min`. They take either several integers or a single set
|
|
405
|
+
or list of integers with a mandatory `default=` seed:
|
|
404
406
|
|
|
405
407
|
```python
|
|
406
|
-
|
|
407
|
-
|
|
408
|
+
Max(a, b) # Max of two values -> b.if_(b > a).else_(a)
|
|
409
|
+
Max(a, b, c) # Max of several values
|
|
410
|
+
Max(s_set, default=0) # Max of a set of integers (TLA: Max(S))
|
|
411
|
+
Min(s_list, default=10) # Min of a list of integers
|
|
408
412
|
```
|
|
409
413
|
|
|
410
|
-
|
|
411
|
-
`
|
|
414
|
+
For collection form, `default` is the runtime reduce seed. The TLA+ backend
|
|
415
|
+
renders this `Max`/`Min` collection reducer with `CHOOSE`. For `MaxOrZero(S)`
|
|
416
|
+
(0 when empty), pass `default=0`:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
def max_or_zero(s_set):
|
|
420
|
+
return Max(s_set, default=0)
|
|
421
|
+
```
|
|
412
422
|
|
|
413
423
|
## 5. Action translation rules
|
|
414
424
|
|
|
@@ -471,8 +481,8 @@ s.msgs = s.msgs | Set(mk_prepared(rm))
|
|
|
471
481
|
|
|
472
482
|
Reserve `f.replace(k, v)` / `rec.replace(**fields)` / `.edit()` for building a
|
|
473
483
|
**new value that is not bound to a state path** — a record to put in a message, a
|
|
474
|
-
helper's return value, or a branch of
|
|
475
|
-
path, write `s.f[k] = v`, not `s.f = s.f.replace(k, v)`.
|
|
484
|
+
helper's return value, or a branch of a conditional value. When the target *is*
|
|
485
|
+
a state path, write `s.f[k] = v`, not `s.f = s.f.replace(k, v)`.
|
|
476
486
|
|
|
477
487
|
Direct assignment already covers multi-field updates (each step is atomic).
|
|
478
488
|
Assignment is immediate, so to read the *unprimed* (pre-update) value of a field
|