Skip to content

SMT soundness: overlapping CASE arms are lowered to ordered IF - #308

Open
lemmy wants to merge 2 commits into
mainfrom
mku-gh304
Open

lemmy wants to merge 2 commits into
mainfrom
mku-gh304

Conversation

@lemmy

@lemmy lemmy commented Oct 14, 2026

Copy link
Copy Markdown
Member

@lemmy lemmy added bug An error, usually in the code. soundness Issues and PRs related to getting TLAPM to prove FALSE labels Oct 14, 2026
@lemmy

This comment was marked as outdated.

lemmy and others added 2 commits October 14, 2026 15:33
CASE arms are unordered (CHOOSE among enabled results), but both SMT
encodings lower CASE to an ordered ite: the default picks the first
enabled arm, `--debug oldsmt` the last.  Overlapping arms with
different results thus yield false proofs.

Tests in test/fast/regression, for SMT, Zenon, and Isabelle:
- case_proved_test.tla: valid obligations that must be proved.
- case_unproved_test.tla: valid obligations a backend does not
  prove; documents incompleteness and pins current behavior.
- case_unsound_test.tla: invalid obligations that must not be proved.

Part of #304
See also tlaplus/rfcs#26

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
A CASE denotes CHOOSE among the results of its enabled arms; arms are
unordered.  Standardize lowered CASE to an ordered ite, so SMT proved
that the first enabled arm wins, e.g. (CASE TRUE -> "u" [] TRUE -> "v")
= "u".

Each arm i but the last now gets an oracle, an uninterpreted Boolean
function case_choice_<n>_<i>_<type> of all guards and results.  Arm i
is selected iff p_i /\ (oracle_i \/ ~(p_{i+1} \/ ... \/ p_n)): the
oracles arbitrate between overlapping arms, and since they are
uninterpreted no proof can rely on which arm wins.  If exactly one
guard holds, the encoding behaves as the ordered ite; otherwise the
value is still one of the enabled results.  Interpreting oracle_i as
[e_i = v], for the CHOOSE'n value v, shows soundness.

Encoding CASE as an opaque term would be sound but would lose disjoint
and OTHER cases, the common use in specs, and whether guards are
disjoint usually depends on hypotheses only the solver sees.  With
oracles, SMT proves everything it did before, minus the false proofs.
CASEs that differ in arm count or arm order remain unprovably equal,
as in Zenon.

Before (proved):

  (not (= (ite true u (ite true v case_error)) u))

After (not proved):

  (declare-fun case_choice_2_1_Idv (Bool Bool Idv Idv) Bool)
  (not (= (ite (and true (or (case_choice_2_1_Idv true true u v)
                             (not true)))
               u
               (ite true v case_error))
          u))

The --debug oldsmt encoding is unchanged and still unsound.

Part of #304
See also tlaplus/rfcs#26

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
@lemmy

lemmy commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

@muenchnerkindl This PR could fix the old SMT encoding, but it currently doesn't. Given the other known issues with that encoding (e.g., #306), I'm not sure it's worth fixing.

Conceptually, the proposed fix seems reasonable to me, but someone with more OCaml expertise should probably review the implementation.

@lemmy
lemmy marked this pull request as ready for review October 14, 2026 23:37

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug An error, usually in the code. soundness Issues and PRs related to getting TLAPM to prove FALSE

Development

Successfully merging this pull request may close these issues.

1 participant