Skip to content

Identity map hardening for guarded entities #817

Closed as not planned
Closed as not planned

Description

@bencap

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.

Activity

  1. added theissue type on Aug 7, 2026
  2. changed the issue type fromtoon Aug 7, 2026
  3. bencap commented on Aug 7, 2026

    @bencap
    CollaboratorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app: backendTask implementation touches the backend

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions