This page traces a single check() from call to verdict, naming every branch where the flow can divert to a deny. It’s the runtime companion to the Architecture overview.
The full path
Step by step
Subject guard
A query with nosubject.idis denied immediately —deny('no-subject')— without touching the network. No subject, no decision.Serialise the payload
toPayload(query)builds the exact wire body:subject.typedefaulted touser,current_aalsnake-cased (defaultaal1),organization/application/resourcedefaulted tonull,contextto{},explainto a boolean. Every key is present — nulls included — matching the PHP client’sDecisionRequest::toArray(). See Wire contract.Cache read (skipped for explain)
Ifexplain: true, the cache is bypassed entirely. Otherwise, if caching is enabled, the SDK computes thecacheKey(a SHA-256 over the canonicalised payload) and returns a fresh hit verbatim. A miss falls through to the network.HTTP with timeout and retry
requestJsonPOSTs the payload to{baseUrl}/{checkPath}withAccept/Content-Type: application/jsonand, if configured,Authorization: Bearer. AnAbortControllerenforcestimeoutMs(default 2000). On a network error / timeout / abort the call retries up toretriestimes (idempotent errors only). A non-2xx response or an unparseable body returnsundefinedimmediately — never retried.Transport failure → deny (uncached)
IfrequestJsonreturnedundefined, the flow yieldsdeny('transport'). Crucially this synthetic deny is not written to the cache — it must not outlive the outage that caused it.Normalise
decisionFromBodyunwraps a single{ data }envelope if present, then reads each field through a type guard with a safe default (missingallowed→false). The result is a typedDecision.Cache write (with policy-version flush)
If a cache key was computed, the verdict is stored forttlMs. If itspolicyVersionexceeds the highest seen, the whole cache is flushed first — a policy change invalidates everything cached under the old one.Return
TheDecisionis returned.can()wraps this whole flow and appliesisGranted(allowed && !requiresStepUp) to reduce it to the fail-safe boolean.
Timeouts and retries
The timeout is per attempt, enforced by aborting the fetch. Retries apply only to idempotent network-level failures (the request may never have reached the server), never to a 4xx/5xx — a server that answered, even with an error, has spoken, and re-asking won’t change the verdict. This mirrors the PHP client’s http_errors => false semantics: HTTP error statuses are data (→ deny), not exceptions to retry.
The verifyToken flow (for contrast)
verifyToken follows a parallel but distinct path: it requires an audience up front, resolves the JWKS (cached 10 min, refetched once on a key-resolution miss), then verifies signature + claims. Unlike check, it rejects rather than returning a deny value — because a token failure has no safe “value”. See Token verification theory.
Next steps
- Wire contract — the exact payload and response.
- Caching decisions — the cache read/write in detail.
- The decision model — normalisation rules.