A container that shows one of several candidates: first match wins.
The candidates are considered in order, and the first whose condition holds is the winner; the winner replaces the container in the resolved output, so a host that can render a document can render a resolved one and nothing downstream needs to know a container was ever there. A candidate with no condition is unconditional and wins if it is reached, so the last candidate with no condition is the default.
Two shapes are refused rather than resolved. A variant with no candidates has nothing to win and would disappear silently. And an unconditional candidate that is not last makes every candidate after it dead: the document says something it cannot mean, and the author is told so at admit time rather than discovering it as a screen that never varies.
iex> node = %{type: "variant", key: "card_notice", nodes: [
...> %{type: "text", key: "card_notice_declined", condition: "context.last_charge_status == 'declined'", text: "Try another card."},
...> %{type: "text", key: "card_notice_default", text: "We will charge the card on file."}
...> ]}
iex> Riddler.Screens.Type.Variant.validate(node)
[]
iex> [finding] = Riddler.Screens.Type.Variant.validate(%{type: "variant", key: "card_notice", nodes: []})
iex> finding.code
"document.empty_variant"