# 7.5 Caching

_Peios / Advanced Peios / eventd / Access Control_

> An access check is a syscall, and a large result cannot afford one per record — the record-level, field-level and descriptor caches.

An access check is a syscall through the full AccessCheck pipeline. A
query returning ten thousand records cannot afford ten thousand of them,
so results are cached — at two levels, plus the descriptor resolution
underneath both.

## 7.5.1 Record-level

When a pattern's descriptor contains **no object ACEs**, the check is a
plain grant or deny on the root, and the result is cached per
`(token, pattern)`.

A query returning ten thousand events across twenty distinct event types
performs at most twenty checks.

## 7.5.2 Field-level

When the descriptor **does** contain object ACEs, the verdict depends on
which fields the record carries, since different payloads produce
different object type lists (§7.3). The result is cached per
`(token, pattern, field set)`.

In practice events of one type carry the same fields, so this is
effectively one check per `(token, event type)`. Log records have a
fixed field set, so log queries reach one check per origin. Metric
records vary by series label keys.

The pathological case is an event type whose payload fields differ from
record to record, which produces a distinct field set — and a distinct
cache entry, and a distinct syscall — for each shape encountered.

## 7.5.3 Descriptor resolution

Resolving a pattern to a descriptor is itself cached, across queries
rather than within one, since it costs a registry read and a hierarchy
walk (§7.2).

eventd watches the security registry subtree and invalidates cached
resolutions and cached check results when a descriptor changes. That is
what makes a revocation take effect on the next query rather than at the
next restart.

If the registry watch fails after startup, eventd **discards the
descriptor cache and operates fail-closed for new resolutions** until
the watch is re-established. A cache it cannot trust to be current is
worse than none: continuing to serve from stale entries would make a
revocation silently ineffective, and the failure would be invisible.
This is a degraded state, not a failure — eventd keeps ingesting, and
keeps answering queries for descriptors already resolved (§9.3).

## 7.5.4 During a stream

Verdicts reached for a streaming query's initial result set are reused
through the watch phase, with two exceptions.

A **new concrete identifier** appearing in a streamed batch — an event
type or log origin not present in the initial results — is resolved and
checked before the record or its distinct value is used. It has never
been authorized, and inheriting a verdict from a sibling pattern would
be a grant nobody made.

A **descriptor change** invalidates the cache as it does anywhere, and
subsequent batches are re-checked against the new one.

The **token** is not re-examined. It was captured at connection (§7.1),
so a client whose memberships change mid-stream continues under what it
connected with, and a client whose access is revoked keeps receiving
records until it disconnects. The bound on that exposure is the client's
own connection lifetime, which for a dashboard may be days.

> [!NOTE]
> Re-obtaining the peer token periodically would narrow the window, and
> would also mean a streaming query could change what it returns halfway
> through for reasons the client cannot see. The snapshot is the simpler
> contract and is what PSPU §3.14 states; the exposure is the cost.
