Address review: string-only application id resolution

Per the Qodo review: only string application ids are recorded in (and
resolved through) _app_node_id_map. Internal ids are commonly integers,
so an integer application id could collide with — and silently remap —
a caller-supplied internal id of the same value. Also pass a labels list
to create_node in the regression test, matching the API signature.
This commit is contained in:
yzxcj797
2026-08-22 02:07:55 +08:00
parent db81136b0a
commit 92ad7bc2df
2 changed files with 33 additions and 6 deletions
+13 -5
View File
@@ -703,11 +703,16 @@ class GraphStore:
strings, while backends such as Neo4j match on internal integer ids
(#1136). Known application ids are resolved to the internal ids the
backend returned at creation time; unknown ids pass through
unchanged, so direct internal-id callers keep working.
unchanged, so direct internal-id callers keep working. Only string
application ids participate: internal ids are commonly integers, so
recording or resolving an integer key could remap a caller-supplied
internal id to a different node.
"""
return self._manager.relationships.create(
self._app_node_id_map.get(start_node_id, start_node_id),
self._app_node_id_map.get(end_node_id, end_node_id),
self._app_node_id_map.get(start_node_id, start_node_id)
if isinstance(start_node_id, str) else start_node_id,
self._app_node_id_map.get(end_node_id, end_node_id)
if isinstance(end_node_id, str) else end_node_id,
rel_type,
properties,
**options,
@@ -820,13 +825,16 @@ class GraphStore:
Backends return their own internal id alongside the stored properties;
when the caller supplied an application id it is preserved in
``properties["id"]`` by the compatibility layer, which makes the pair
recoverable (#1136).
recoverable (#1136). Only STRING application ids are recorded:
internal ids are commonly integers, and an integer application id
would collide with (and silently remap) a caller-supplied internal id
of the same value in ``create_relationship``.
"""
if not isinstance(created, dict):
return
app_id = (created.get("properties") or {}).get("id")
internal_id = created.get("id")
if app_id is not None and internal_id is not None:
if isinstance(app_id, str) and internal_id is not None:
self._app_node_id_map[app_id] = internal_id
# Compatibility with AgentMemory / ContextGraph interface
@@ -139,11 +139,30 @@ class AppIdResolutionTests(unittest.TestCase):
def test_create_node_also_populates_the_map(self) -> None:
store, manager = make_store()
store.create_node("Person", {"id": "app-1", "name": "Alice"})
store.create_node(["Person"], {"id": "app-1", "name": "Alice"})
store.add_edges([{"source_id": "app-1", "target_id": 42, "type": "knows"}])
self.assertEqual(manager.relationships.calls, [(100, 42, "knows")])
def test_integer_application_ids_never_collide_with_internal_ids(self) -> None:
# Qodo review: an int application id must not be recorded/resolved,
# or a caller passing that same int as an internal id would be
# silently remapped to a different node.
store, manager = make_store()
store.add_nodes(
[
{"id": 100, "type": "Person", "properties": {}},
{"id": "str-app", "type": "Person", "properties": {}},
]
)
# Internal id 100 legitimately targets the FIRST node; the integer
# app id of that same value must not redirect it to the second.
store.create_relationship(100, 101, "RELATES_TO")
self.assertEqual(manager.relationships.calls, [(100, 101, "RELATES_TO")])
self.assertNotIn(100, store._app_node_id_map)
def test_nodes_without_application_id_do_not_pollute_the_map(self) -> None:
store, manager = make_store()