From 58dcc1004934c300aaa2ab5b513791e96b87df3e Mon Sep 17 00:00:00 2001 From: caya8205-2 Date: Sat, 29 Aug 2026 19:54:52 +0700 Subject: [PATCH] fix(gateway): memoize only resolved profile homes, never the miss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on the memo introduced with _profile_home_for_key. The problem is sharper than "no invalidation on profile deletion": caching the miss pinned a profile that appears AFTER the gateway started to the ambient store for the life of the process, which is the exact failure this helper exists to prevent. That is not hypothetical — an enrollment bridge can provision profiles// at runtime, so a key is legitimately seen before its directory exists. Memoize hits only. A miss costs one profile_exists() stat and recurs only for profiles that genuinely do not exist, so the hot path for real profiles is still a dict hit. Co-Authored-By: Claude Opus 5 --- gateway/session.py | 9 ++++++++- ...test_multiplex_session_db_profile_scope.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gateway/session.py b/gateway/session.py index 9fa3ed15c7..b0a6c798f4 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -1425,7 +1425,14 @@ class SessionStore: "Could not resolve profile home for %r: %s", session_key, exc ) home = None - cache[profile] = home + # Only a hit is memoized. A profile directory can appear *after* the + # gateway started — the enrollment bridge provisions profiles// + # at runtime — and caching the miss would pin that profile's rows to + # the ambient store for the life of the process, which is the bug + # this helper exists to prevent. A miss costs one profile_exists() + # stat and only recurs for profiles that genuinely do not exist. + if home is not None: + cache[profile] = home return home def _db_for_key(self, session_key: Optional[str]): diff --git a/tests/gateway/test_multiplex_session_db_profile_scope.py b/tests/gateway/test_multiplex_session_db_profile_scope.py index ca6580ef5f..5980248519 100644 --- a/tests/gateway/test_multiplex_session_db_profile_scope.py +++ b/tests/gateway/test_multiplex_session_db_profile_scope.py @@ -606,3 +606,22 @@ def test_pinned_handle_still_wins_over_key_resolution(multiplex_homes): store._db = sentinel assert store._db_for_key("agent:fitness:telegram:dm:1") is sentinel assert store._db_for_session_id("whatever") is sentinel + + +def test_profile_home_is_not_memoized_before_the_profile_exists(multiplex_homes): + """A profile provisioned after startup must not stay pinned to the root store. + + The enrollment bridge creates ``profiles//`` at runtime, so a key can + be seen before its directory exists. Memoizing that miss would pin the + profile to the ambient store for the life of the process — the exact bug + this helper exists to prevent. + """ + root, _profile = multiplex_homes + store = _multiplex_store(root) + key = "agent:latecomer:telegram:dm:9" + + assert store._profile_home_for_key(key) is None + + (root / "profiles" / "latecomer").mkdir(parents=True) + + assert store._profile_home_for_key(key) == root / "profiles" / "latecomer"