Files
DumiFlex 05d53a0c6c fix(wildcard): an excluded pool emits nothing instead of the first option
_pick_weighted returned options[0] when every weight was zero. That looked
like a working pick - a real option, right shape, right place - and stayed
wrong silently until the list was reordered and the choice changed. It hid
both over-narrowed constraints and a wildcard whose options were all
disabled.

Now returns None, which every caller already turned into an empty binding.
That is what the null option means, so the engine had the vocabulary for
this already.

Applies to all three pools that constraints can zero: the chain handler, the
nested-@{} path in the resolver, and the SPA preview. The inline {a|b}
branch weights are left alone - a user writing {0::a|0::b} is a different
situation and constraints never touch it.

Three tests asserted the old behaviour and are rewritten to state the new
one; the constraint_excludes_all_options warning stays, and now
distinguishes an over-narrowed pool from a legitimate null roll.
2026-08-18 17:38:05 +03:00

121 lines
3.8 KiB
Python

"""An over-narrowed pool emits nothing rather than a plausible wrong option.
`_pick_weighted` used to return `options[0]` when every weight was zero. That
looks like a working pick — it is a real option, in the right place, with the
right shape — right up until you reorder the list and the "choice" changes.
The engine already has a first-class way to say "no output": the null option.
An excluded pool now says the same thing.
"""
import random
import pytest
from engine.modules.wildcard_handler import WildcardHandler, _pick_weighted
def _opts(*weights):
return [
{"id": f"o{i}", "value": f"v{i}", "weight": w}
for i, w in enumerate(weights)
]
def _ctx():
return {
"__wp_rng__": random.Random(0),
"__wp_node_seed__": 42,
"__wp_warnings__": [],
"__wp_current_module_id__": "abc12345",
}
def test_all_zero_weights_picks_nothing():
assert _pick_weighted(_opts(0, 0, 0), random.Random(0)) is None
def test_empty_pool_still_picks_nothing():
assert _pick_weighted([], random.Random(0)) is None
def test_a_single_live_option_still_wins():
# The floor case: exactly one survivor must still be pickable, or the fix
# would trade a wrong answer for no answer.
chosen = _pick_weighted(_opts(0, 3, 0), random.Random(0))
assert chosen is not None
assert chosen["id"] == "o1"
@pytest.mark.parametrize("seed", range(8))
def test_weighting_is_untouched_when_anything_is_live(seed):
picked = _pick_weighted(_opts(0, 1, 0, 0), random.Random(seed))
assert picked is not None and picked["id"] == "o1"
def test_wildcard_with_every_option_disabled_binds_empty():
"""Weight 0 is documented as "disable without deleting". All of them
disabled therefore means nothing is selectable — not "quietly use the
first one anyway"."""
payload = {
"options": [
{"id": "o1", "value": "tee", "weight": 0},
{"id": "o2", "value": "gown", "weight": 0},
],
"sub_categories": [],
"var_binding": "outfit",
}
out = WildcardHandler.resolve(payload, {"variable_binding": "outfit"}, _ctx())
assert out == {"outfit": ""}
def test_constraint_excluding_everything_binds_empty_and_warns():
"""The reported shape, one level up: two garments whose menus share no
shoe intersect to nothing. Previously the target returned its first
option forever."""
ctx = _ctx()
source = {
"type": "wildcard",
"id": "src00001",
"payload": {
"options": [{"id": "s1", "value": "tee", "sub_categories": ["formal"]}],
"sub_categories": ["formal"],
"var_binding": "outfit",
},
"instance": {"variable_binding": "outfit"},
}
target_payload = {
"options": [
{"id": "t1", "value": "sneakers", "weight": 1, "sub_categories": ["casual"]},
{"id": "t2", "value": "boots", "weight": 1, "sub_categories": ["casual"]},
],
"sub_categories": ["casual"],
"var_binding": "shoes",
}
from engine.pipeline import PipelineEngine
out = PipelineEngine().run([
source,
{
"type": "constraint",
"id": "con00001",
"payload": {
"source_wildcard_id": "src00001",
"target_wildcard_id": "tgt00001",
"matrix": {"formal": {"casual": {"mode": "exclude", "factor": 0.0}}},
"exceptions": [],
},
"instance": {},
},
{
"type": "wildcard",
"id": "tgt00001",
"payload": target_payload,
"instance": {"variable_binding": "shoes"},
},
], seed=1)
assert out["shoes"] == ""
assert any(
w["type"] == "constraint_excludes_all_options"
for w in out["__wp_warnings__"]
)
_ = ctx