You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closes the one reachability path the tripwire structurally cannot see, and states the guarantee precisely.
Problem
The tripwire fires on SQL execution. session.get() on a warm identity map emits no SQL, so neither the tripwire nor any clause applies. The named path deliberately loads unscoped, so a row the caller may not see can sit in the session.
Verified:
Step
Hook fires?
Unscoped named load
yes
session.get() on the warm cache
no — returns the same object
Fresh scoped query
yes — correctly returns 0 rows
session.get() after expunge
yes — re-queried and filtered
Acceptance criteria
A Session subclass forces populate_existing=True for guarded entities in get(), wired via sessionmaker(class_=...).
A test proves session.get() on a guarded entity re-queries and is filtered even on a warm identity map.
fetch_authorized expunges on failed assert only when that call loaded the entity — expunging a pre-existing object detaches earlier references and raises DetachedInstanceError on later attribute access.
The guard module docstring states what is and is not guaranteed: queries are filtered, not the session contains only visible rows, and text() bypasses all of it.
Not planned — the problem no longer exists under the revision of #808 to Postgres row-level security.
The identity-map gap came from the named path deliberately loading unscoped, which meant a row the caller could not see might sit in the session where neither the tripwire nor any loader criteria applied.
Under RLS the serving role cannot load an invisible row in the first place, so the session physically cannot contain one. populate_existing, the Session subclass, and expunge-on-failed-assert are all unnecessary.
One residual rule worth carrying into the GUC plumbing issue rather than keeping this one open: never reuse a session across principals. The GUC is transaction-scoped but the identity map spans transactions, so a session that changed principals mid-life could mix visibilities. The api is per-request and the worker runs as the owner, so nothing does this today.
Activity
bencap commentedon Aug 7, 2026
Not planned — the problem no longer exists under the revision of #808 to Postgres row-level security.
The identity-map gap came from the named path deliberately loading unscoped, which meant a row the caller could not see might sit in the session where neither the tripwire nor any loader criteria applied.
Under RLS the serving role cannot load an invisible row in the first place, so the session physically cannot contain one.
populate_existing, theSessionsubclass, and expunge-on-failed-assert are all unnecessary.One residual rule worth carrying into the GUC plumbing issue rather than keeping this one open: never reuse a session across principals. The GUC is transaction-scoped but the identity map spans transactions, so a session that changed principals mid-life could mix visibilities. The api is per-request and the worker runs as the owner, so nothing does this today.