112d3107ef
MultiCriterion and ChoiceCriterion were near-duplicate copies whose INCLUDES branches had drifted — the exclude-only bug existed in one but not the other. Extract the shared include/exclude/null set-membership logic into a _SetCriterion base implemented once (INCLUDES with empty-list guards, EQUALS as an alias, IS_NULL/NOT_NULL); subclasses contribute only their value type and their own modifiers via _extra_q (INCLUDES_ALL for Multi; EXCLUDES/NOT_EQUALS for Choice). Behaviour preserved (full modifier vocabulary kept); the duplication that caused the drift is gone. Surfacing the modifier axis and harmonizing EXCLUDES is tracked in #10. https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS