Skip to main content
Everything in this page is opt-in. A client without metrics emits nothing, and a provider whose capabilities().metrics is false stores nothing. For the guide, see Metrics.

AlfizClientOptions.metrics

MetricsObserver | MetricsObserver[]
required
(observation: CheckObservation) => void, called synchronously from the check path. An array fans out, isolating failures per sink. Do cheap work only, because a slow observer is latency on your request path.
number | { gate, visibility }
default:"1"
The probability an evaluated check is observed. A number applies to every shape; the object form samples gates (can, require) and visibility traffic (canAny, requireAny, holds, heldKeys) separately. Evaluated with one Math.random() inside the call, before any observation is built. Values are clamped to [0, 1].
In the object form, gate defaults to 1 but visibility defaults to the resolved gate rate, not to 1. So { gate: 0.1 } samples visibility traffic at 0.1 too. Set both explicitly whenever you set either, and { gate: 1, visibility: 0.02 } is the shape you usually want, because one render fires hundreds of visibility checks and gates correspond one-to-one with user actions.
() => number
default:"Math.random"
The randomness source for sampling. Injectable because Math.random cannot be seeded and a sampled pipeline still has to be testable.
"type" | "instance" | ScopeType[]
default:"\"type\""
Cardinality policy for the scope dimension. "type" folds every instance into its scope type. An array opts the listed scope types into raw instance counting. "instance" opts in everything.
(error: unknown) => void
Receives errors thrown by an observer. Unset means silently ignored. right default for a lossy counter, the wrong one for debugging it.

CheckObservation

boolean
Present and true when this check named a permission admitted by externalPermissions: "warn" or "allow", meaning one no catalog declares. This is how you count what still needs an imports declaration, from the same stream that counts everything else.
boolean
true for can and require, the shapes that authorize an action. false for the visibility shapes, which drive conditional UI and vastly outnumber gates. Keep the two apart in any counter you build.
string | null
The concrete key that decided the check (the allowing key on an allow, the first key otherwise), the pattern for canAny / requireAny, or null for heldKeys, which asks about the whole catalog at once.
string
The scope type checked, or "*" for the global scope. Always bounded.
string
The scope instance, present only for scope types opted into instance counting via scopeInstances.
string | null
Set when exactly one grant row allowed the check. That row was the sole matcher: revoking it would have flipped this check to deny. null when several rows allowed, or when the check was denied.
boolean
The allow came from ancestor implication rather than a direct match. The implying grants are still reported in matchedGrantIds.
number
The probability that kept this observation. Multiply by 1 / sampleRate to estimate real volume.

CheckOptions

Every check shape accepts an optional trailing { observe?: boolean }, and client.snapshot() accepts observe in its options. Setting it false evaluates normally and records nothing.
View-as sessions set this on the previewed side automatically: an administrator looking through someone else’s eyes did not use that person’s grants, and attribution never follows the preview.

otelMetricsObserver(options)

@opentelemetry/api is not a dependency of Alfiz. OtelMeter is the structural subset the adapter uses: createCounter(name, options) returning something with add(value, attributes), which a real Meter satisfies without a cast. Emits alfiz.checks, and with attribution on, alfiz.grant.matched, alfiz.grant.sole_match, alfiz.revoke.matched, and alfiz.role.matched. With extrapolate on (the default), each measurement adds 1 / sampleRate so a sampled counter still reads as real traffic. At the default sampleRate: 1 the two are identical.

createMetricsAggregator(options?)

A pure, bounded, windowed fold over the observation stream.
Windows roll lazily, on the first observation past the boundary, so a quiet process schedules nothing. startMetricsFlusher(aggregator, { intervalMs }) adds a timer when a window must close on time rather than on traffic; it returns a stop() function and never keeps a process alive.

MetricsBatch

Counters carry observed (what was seen) and estimated (corrected for sampling) side by side.

createProviderMetricsSink(provider, options?)

Aggregates locally, then batches to a provider that stores usage. Accepts every createMetricsAggregator option plus intervalMs and maxPendingBatches (default 4).
Delivery is fire-and-forget: nothing awaits the chain, so a slow or failing store cannot add time to a check or a write. When more than maxPendingBatches are queued behind a slow store, new batches are dropped and counted on sink.droppedBatches rather than queued. The sink checks capabilities().metrics once; a provider that does not accept metrics is simply never sent any, and the sink degrades to a local aggregator.

Usage reads

Available on any provider whose capabilities().metrics is true, meaning the Application with metrics: {}, and hosted providers pointed at one.
query is { ids?: string[]; since?: number; until?: number }. ids filters to specific rows or keys, since is inclusive and until exclusive, both epoch ms, defaulting to the provider’s full retention window.
Gate and visibility counts are reported separately and never summed, since forty thousand renders and twelve actions are different numbers. buckets (0.5.1) is the same per-bucket series RowUsage carries: the totals are exactly that list summed, so one read gives you both the headline number and the shape of it over time. Buckets with no traffic are absent rather than zero-filled. You know the window and the granularity, so a dense axis is yours to fill.

revocationSafeguard(usage, options?)

Pure, so bind it in any UI. It keys on soleMatch, never on matched, and never claims that an unused grant is safe to remove.

Application options

Requires a storage driver implementing recordMetrics, readMetrics, and pruneMetrics, which the memory and Prisma drivers do. Construction throws if they are missing, rather than accepting batches that go nowhere. The Prisma schema fragment adds one model, AlfizMetric; retention compaction runs opportunistically and off the write path.