/**
 * verify/claims — reading a verified claim set, one rule per kind of claim.
 *
 * Three readings, and the difference between them is the whole file:
 *
 *   • **A roles claim is a LIST.** An array contributes each string entry as
 *     one role; a single string is ONE role. `"Not bot-users"` is the role
 *     `Not bot-users` — never the two roles `Not` and `bot-users`, which is how
 *     a space-splitting reader lets a group name satisfy a role gate it does
 *     not name. (`'space-delimited'` keeps the old reading, by request only.)
 *   • **A scope claim is space-delimited** — RFC 8693 §4.2 / RFC 9068 §2.2.3
 *     define `scope` that way, and Entra's `scp` follows it. An array is also
 *     read, entry by entry.
 *   • **A claim NAME is never split.** `urn:bot:objectguid` and
 *     `http://schemas.microsoft.com/…/role` contain dots and colons and are one
 *     name each. A nested claim is named by a PATH — an array of names
 *     (`['realm_access', 'roles']` for Keycloak) — never by a dotted string.
 *
 * Comparison is always exact, and nothing here trims, case-folds or tidies a
 * value: ids are opaque bytes (rule 7).
 */
/** A top-level claim name, or a path of names into nested objects. */
export type ClaimPath = string | readonly string[];
/** How a roles claim's single STRING is read. */
export type RolesFormat = 'list' | 'space-delimited';
/** The value at `path`, or `undefined`. A string path is ONE name. */
export declare function claimAt(payload: Readonly<Record<string, unknown>>, path: ClaimPath): unknown;
/** A path, spelled for a banner or a refusal — never a value. */
export declare function pathLabel(path: ClaimPath): string;
/**
 * Roles out of a claim value, never invented: ABSENT when the claim is absent,
 * empty, or holds no string.
 */
export declare function rolesOf(value: unknown, format: RolesFormat): readonly string[] | undefined;
/**
 * Scopes out of a scope claim: a space-delimited string or an array of
 * strings. ABSENT when the claim is absent or holds nothing — which is not the
 * same fact as "holds no matching scope", and the person test tells them apart.
 */
export declare function scopesOf(value: unknown): readonly string[] | undefined;
/**
 * The claims that name the client which OBTAINED a token, in the order they
 * are read: `azp` (OIDC, Entra v2, Keycloak), `appid` (Entra v1, AD FS), `cid`
 * (Okta), `client_id` (RFC 9068).
 */
export declare const CLIENT_CLAIMS: readonly string[];
/**
 * The client that obtained the token: the FIRST of {@link CLIENT_CLAIMS} that
 * is present. A present claim that is not a non-empty string names nobody, and
 * the answer is `undefined` — never a fall-through to the next claim, which
 * would let a token choose which of its claims is read.
 */
export declare function clientOf(payload: Readonly<Record<string, unknown>>): string | undefined;
