pgvpd 0.9.0

Virtual Private Database for PostgreSQL — tenant isolation at the connection level
# ──────────────────────────────────────────────────────────────
# pgvpd Context Resolvers
# ──────────────────────────────────────────────────────────────
# Each [[resolver]] block defines a SQL query that runs post-auth
# to derive session variables from database state.
#
# Resolvers execute in dependency order. Each can reference results
# from prior resolvers via the `params` bind list.
#
# Fields:
#   name        Unique name for this resolver
#   query       SQL query with $1, $2, ... bind parameters
#   params      List of context variable names to bind as parameters
#   inject      Map of { "session_var" = "column_name" } to inject
#   required    If true, no rows = connection terminated (default: false)
#   depends_on  List of resolver names that must run first
#   cache_ttl   Seconds to cache results (0 = no caching, default: 0)
#
# Referenced from pgvpd.conf via:
#   resolvers = resolvers.toml
# ──────────────────────────────────────────────────────────────

# ─── Step 1: Resolve org membership from user ID ─────────────

[[resolver]]
name = "org_membership"
query = """
  SELECT org_id::text, role
  FROM org_memberships
  WHERE user_id = $1::uuid AND is_active = true
  LIMIT 1
"""
params = ["app.user_id"]
inject = { "app.org_id" = "org_id", "app.org_role" = "role" }
required = false
cache_ttl = 300

# ─── Step 2: Resolve team memberships ────────────────────────

[[resolver]]
name = "team_memberships"
query = """
  SELECT array_agg(tm.team_id)::text AS team_ids
  FROM team_memberships tm
  JOIN teams t ON t.id = tm.team_id
  WHERE tm.user_id = $1::uuid
    AND t.org_id = $2::uuid
    AND t.is_active = true
"""
params = ["app.user_id", "app.org_id"]
inject = { "app.team_ids" = "team_ids" }
required = false
depends_on = ["org_membership"]
cache_ttl = 300

# ─── Step 3: Resolve case grants (ACL) ──────────────────────

[[resolver]]
name = "case_grants"
query = """
  SELECT array_agg(DISTINCT case_id)::text AS granted_case_ids
  FROM (
    SELECT case_id FROM case_user_access WHERE user_id = $1::uuid
    UNION
    SELECT case_id FROM case_team_access WHERE team_id = ANY($2::uuid[])
  ) grants
"""
params = ["app.user_id", "app.team_ids"]
inject = { "app.granted_case_ids" = "granted_case_ids" }
required = false
depends_on = ["team_memberships"]
cache_ttl = 60