What a receiving thread's step does to the refinement invariant.
The .rx thread is the one part of the compiled algorithm with no source counterpart at all: it
owns no label and its step consumes none, so no source process ever schedules it, and the source
therefore stutters while it runs. That is only sound if the step is invisible — and "invisible"
here is a statement about procRelatesTo, not about states being equal, because an rx step does
change the target's memory and FIFOs.
What it changes, it changes in exactly the way the invariant already accounts for.
procRelatesTo says the source's queue at the process's channel is the target's queue with the
process's inbox in front of it. An rx step moves one value across that boundary: off the head of
the target's FIFO, onto the end of inbox. The concatenation is the same either way, which is why
the source needs to take no step to keep up.
The trace is 1. Reception is not in Behavior's alphabet
(Core/GuardedPlusCal/Semantics/Denotational.lean), which is what makes stuttering admissible
rather than an observable the source failed to produce.
A receiving thread's step is invisible to the source. The value it moves comes off the head
of the target's FIFO and goes onto the end of inbox, so the source's queue — which the invariant
says is inbox ++ target's queue — is unchanged, and the source keeps up by not moving.
Stated as the transformation of one InboxState: everything procRelatesTo and algRelatesTo ask
about the pair ⟨key, contents⟩ still holds of ⟨key, contents ++ [v]⟩ against the stepped target.
No label is mentioned — a .rx thread owns none, and its step produces .none.
inbox ∉ Ref.freeVars c is the same freshness this pass carries everywhere: without it the channel
reference could resolve to a different key under the target's memory than under the source's, and
the two sides' ChanKeys would not be the one the invariant names.
The same step, at the process level. rxStep_step with procRelatesTo's clauses assembled
around it, which is the form the algorithm level meets: a receiving thread's step is one whole
Algebra.step of the target, and the source answers it with no step at all.
The label set survives untouched: a .rx thread owns no label and its step consumes none, so
procRelatesTo's L₂ = L₁ needs nothing done to it. That is what makes the extra thread invisible
to the label bookkeeping as well as to the memory.
The FIFO clauses are returned rather than folded in: they belong to algRelatesTo, which quantifies
over all instances' keys at once, so only their per-instance content can be established here.
And at the algorithm level: the source does not move at all. One instance takes a receiving
thread's step; every other instance and every other FIFO key is untouched, so the whole
algRelatesTo witness survives with one instance's InboxState extended by the value that moved.
This is the rx half of the per-step obligation the algorithm-level refinement discharges. It is
answered with zero source steps — Relation.star.refl — which is why the source side of that
refinement has to be Relation.star Aₛ.step rather than Aₛ.step: GuardedPlusCal.Algebra.reducing
is defined as that star, so this is the goal's own shape rather than a weakening of it.