fix(provider): scan rejects non-dict sort with InvalidArguments, never AttributeError
heid-bug-hunt panel (Gróa + Hulda, confirmed-from-code) caught that a truthy
non-dict `sort` (e.g. sort="updated_at" or sort=["updated_at"]) reached
`(sort or {}).get(...)` and crashed with AttributeError instead of the
InvalidArguments PRE-003 promises for malformed caller-controlled input. Add an
isinstance guard before field extraction. Test covers str/list/int sort values.
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ratatoskr"
|
name = "ratatoskr"
|
||||||
version = "0.20.12"
|
version = "0.20.13"
|
||||||
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -383,6 +383,8 @@ class RatatoskrMemoryStore:
|
|||||||
scope_all = scope_all or {}
|
scope_all = scope_all or {}
|
||||||
scope_any = scope_any or []
|
scope_any = scope_any or []
|
||||||
_validate_scope(scope_all, scope_any) # PRE-002 (same lattice as search)
|
_validate_scope(scope_all, scope_any) # PRE-002 (same lattice as search)
|
||||||
|
if sort is not None and not isinstance(sort, dict): # PRE-003: malformed sort => reject, never crash
|
||||||
|
raise InvalidArguments("sort must be an object with field and direction")
|
||||||
field = (sort or {}).get("field", "updated_at")
|
field = (sort or {}).get("field", "updated_at")
|
||||||
direction = (sort or {}).get("direction", "desc")
|
direction = (sort or {}).get("direction", "desc")
|
||||||
if field not in _SORTABLE_FIELD_NAMES or direction not in ("asc", "desc"): # PRE-003
|
if field not in _SORTABLE_FIELD_NAMES or direction not in ("asc", "desc"): # PRE-003
|
||||||
|
|||||||
@@ -427,6 +427,14 @@ async def test_scan_unadvertised_sort_field_rejected():
|
|||||||
await store.scan(scope_all={"end_user": "u1"}, limit=3, sort={"field": "salience", "direction": "desc"})
|
await store.scan(scope_all={"end_user": "u1"}, limit=3, sort={"field": "salience", "direction": "desc"})
|
||||||
|
|
||||||
|
|
||||||
|
async def test_scan_non_dict_sort_rejected():
|
||||||
|
# PRE-003: a truthy non-dict sort (caller-controlled) -> InvalidArguments, never AttributeError.
|
||||||
|
store = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
|
||||||
|
for bad in ("updated_at", ["updated_at"], 5):
|
||||||
|
with pytest.raises(InvalidArguments):
|
||||||
|
await store.scan(scope_all={"end_user": "u1"}, limit=3, sort=bad)
|
||||||
|
|
||||||
|
|
||||||
async def test_scan_records_carry_person_prime_filter_fields():
|
async def test_scan_records_carry_person_prime_filter_fields():
|
||||||
# The client _scan_filter_matches keys on agent_id + subject + worldtree_scope; a record
|
# The client _scan_filter_matches keys on agent_id + subject + worldtree_scope; a record
|
||||||
# missing any is silently dropped -> the scan record must carry them verbatim.
|
# missing any is silently dropped -> the scan record must carry them verbatim.
|
||||||
|
|||||||
Reference in New Issue
Block a user