Raxx · internal docs

internal · gated

ADR-0056: Permission Resolution — Session-Embedded Cache (Option A)

Date: 2026-05-09 UTC
Status: Accepted
Deciders: software-architect
Refs: docs/architecture/rbac-design.md §8, docs/architecture/rbac-v2/design.md §3.4


Context

The permission resolution algorithm (user → groups → roles via DAG walk → permissions) involves multiple DB joins and a DAG traversal on every request. At v1 scale (single operator, small team), this is acceptable on every request. But as the console grows, the cost compounds. Three caching strategies were evaluated (per rbac-design.md §8):


Decision

Option A: Session-embedded permission cache.

On login (when FLAG_RBAC_V2 is on), the permission resolution runs once and the result is stored in the session record as a JSONB column. Every subsequent request reads from the session — no DAG traversal required.

Cache invalidation: whenever rbac_user_groups or rbac_group_roles changes for a user, the post-grant service invalidates that user's session cache by clearing the JSONB column. The next request triggers a recompute.

Ticket-scoped grants are not cached in the session. They are checked live per request because their validity can change between requests (ticket closure).

Break-glass sessions maintain a separate in-memory marker; the session cache is not used for break-glass active checks.


Consequences

Positive: - No additional infra dependency (no Redis, no materialized view maintenance job). - The DAG traversal happens once per login, not per request. - Invalidation is exact: the affected user's cache is cleared immediately on any grant/revoke, not after a TTL window. - Compatible with the existing session model; the Console Postgres is already the session store.

Negative: - If the session record is in a read-only state (e.g., during a DB maintenance window), cache invalidation cannot be written. Mitigation: the grant service handles this gracefully — if cache invalidation fails, the old permission set is used until the session expires or the user logs in again. This is a degraded-but-not-broken state. - At very high console operator counts (>100), the session-embedded approach may produce stale reads in scenarios where many operators are active simultaneously and their caches need invalidating. At v1 scale (single operator), this is not a concern. Revisit at 100+ operators.


Alternatives Considered

Option B: Postgres materialized view

Requires a trigger or explicit refresh on every write to the five relevant tables. The refresh is cheap at low write rates. But adds materialized view complexity to the schema and introduces a lag between the write and the view refresh if deferred. Correct for a read-heavy multi-tenant admin console; premature for v1.

Option C: Redis TTL cache

Adds a Redis infra dependency. TTL-based invalidation means a 5-minute window where a revoked permission is still cached. For RBAC, 5-minute stale access after a revoke is a security concern. The exact invalidation of Option A is preferable.


See docs/architecture/rbac-v2/design.md §3.4 for the full resolution algorithm.