Compare commits

...

3 Commits

Author SHA1 Message Date
xmarre
98a7e14aa4
Merge dbed5a1b52 into 0904cc3fe5 2026-03-15 06:39:16 +00:00
xmarre
dbed5a1b52 Replace sanitize and hash passes 2026-03-15 07:39:10 +01:00
xmarre
24fdbb9aca Replace sanitize hash two pass 2026-03-15 07:30:18 +01:00
2 changed files with 33 additions and 9 deletions

View File

@ -170,17 +170,17 @@ def _signature_to_hashable_impl(obj, depth=0, max_depth=_MAX_SIGNATURE_DEPTH, ac
return _FAILED_SIGNATURE
key_value, key_sort = key_result
value_value, value_sort = value_result
ordered_items.append((((key_sort, value_sort)), (key_value, value_value)))
ordered_items.append((key_sort, value_sort, key_value, value_value))
ordered_items.sort(key=lambda item: item[0])
ordered_items.sort(key=lambda item: (item[0], item[1]))
for index in range(1, len(ordered_items)):
previous_sort_key, previous_item = ordered_items[index - 1]
current_sort_key, current_item = ordered_items[index]
if previous_sort_key == current_sort_key and previous_item != current_item:
previous_key_sort = ordered_items[index - 1][0]
current_key_sort = ordered_items[index][0]
if previous_key_sort == current_key_sort:
return _FAILED_SIGNATURE
value = ("dict", tuple(item for _, item in ordered_items))
sort_key = ("dict", tuple(sort_key for sort_key, _ in ordered_items))
value = ("dict", tuple((key_value, value_value) for _, _, key_value, value_value in ordered_items))
sort_key = ("dict", tuple((key_sort, value_sort) for key_sort, value_sort, _, _ in ordered_items))
elif obj_type is list or obj_type is tuple:
try:
items = list(obj)
@ -417,9 +417,11 @@ class CacheKeySetInputSignature(CacheKeySet):
return _signature_to_hashable(signature)
async def get_immediate_node_signature(self, dynprompt, node_id, ancestor_order_mapping):
"""Build the cache-signature fragment for a node's immediate inputs.
"""Build the immediate cache-signature fragment for a node.
Link inputs are reduced to ancestor references, while raw values are sanitized first.
Link inputs are reduced to ancestor references here, while non-link
values are appended as-is. Full canonicalization happens later in
`get_node_signature()` via `_signature_to_hashable()`.
"""
if not dynprompt.has_node(node_id):
# This node doesn't exist -- we can't cache it.

View File

@ -240,6 +240,28 @@ def test_signature_to_hashable_fails_closed_for_ambiguous_dict_ordering(caching_
assert isinstance(sanitized, caching.Unhashable)
def test_signature_to_hashable_fails_closed_on_dict_key_sort_collisions_even_with_distinct_values(caching_module, monkeypatch):
"""Different values must not mask dict key-sort collisions during canonicalization."""
caching, _ = caching_module
original = caching._signature_to_hashable_impl
key_a = object()
key_b = object()
def colliding_key_canonicalize(obj, *args, **kwargs):
"""Force two distinct raw keys to share the same canonical sort key."""
if obj is key_a:
return ("key-a", ("COLLIDE",))
if obj is key_b:
return ("key-b", ("COLLIDE",))
return original(obj, *args, **kwargs)
monkeypatch.setattr(caching, "_signature_to_hashable_impl", colliding_key_canonicalize)
sanitized = caching._signature_to_hashable({key_a: 1, key_b: 2})
assert isinstance(sanitized, caching.Unhashable)
@pytest.mark.parametrize(
"container_factory",
[