/**
 * verify/personTest — only a person's token is a person (rule 5).
 *
 * A verified signature proves who SIGNED a token, not that it stands for a
 * person. On Entra ID any application registered in the tenant can obtain an
 * app-only token for this API by the client-credentials grant — right issuer,
 * right audience, a valid signature and an `oid` — and Microsoft says so:
 * "app-only tokens can be issued without a `roles` claim. Applications that
 * expose APIs must implement permission checks in order to accept tokens."
 * A verifier that accepted it would hand a service principal a `principal`,
 * conversations, a model budget and the agent's tools.
 *
 * Five checks, in this order, because each names a different shape:
 *
 *   c. `idtyp: "app"` — Entra's own marker, refused whenever present.
 *   d. `oid` equal to `sub` — Microsoft's documented app-only test.
 *   b. roles and NO scope claim at all — a daemon's token carries app roles
 *      instead of delegated scopes.
 *   a. the required scope in the scope claim — Microsoft: `scp` is "Only
 *      included for user tokens".
 *   e. the client that obtained the token is one this deployment lists — on
 *      Okta and Keycloak a client-credentials token may carry scopes too, and
 *      this is the check that refuses it there.
 *
 * a–d fail as `not-a-user-token`; e fails as `wrong-client`. `scp` alone is
 * Microsoft's test, not an IdP-neutral one; the checks together are.
 *
 * Check b is DEFENCE IN DEPTH, kept on purpose: today no token's outcome
 * depends on it, because check a refuses every token with no scope claim in
 * the same words. It stays so that a later change to a — an optional scope, a
 * scope read from elsewhere — cannot silently admit a daemon's roles-only
 * token. (A mutation that deletes it survives the suite, and that is why.)
 *
 * **The client check is load-bearing on Keycloak and Okta**, where a service
 * account's client-credentials token carries your API's scope: every client in
 * `allowedClients` must have service accounts / client credentials turned OFF,
 * and `'any'` is refused in production by `identityFromConfig`.
 */
import type { IdentityFailureClass } from '../../../hosting/errors.js';
import { type ClaimPath } from './claims.js';
/** What the person test is configured with. */
export interface PersonTestOptions {
    /** The OAuth scope only this API's person tokens carry. One scope, exact. */
    readonly requiredScope: string;
    /** Where the scopes are: `'scp'` (Entra, AD FS) or `'scope'` (RFC 9068, Keycloak, Okta). */
    readonly scopeClaim: ClaimPath;
    /** Clients allowed to obtain a person token for this API, or `'any'` (the check is off). */
    readonly allowedClients: readonly string[] | 'any';
    /** The configured roles claim — part of check b's "roles without a scope". */
    readonly rolesClaim: ClaimPath;
}
/** The failure class, or `undefined` when the token stands for a person. */
export declare function personTestFailure(payload: Readonly<Record<string, unknown>>, options: PersonTestOptions): Extract<IdentityFailureClass, 'not-a-user-token' | 'wrong-client'> | undefined;
/**
 * Unknown is not none (rule 14). When the roles claim was REPLACED by a
 * pointer to somewhere else — OIDC's distributed claims (`_claim_names`), which
 * is what Entra sends instead of `groups` above 200 groups, or Entra's
 * `hasgroups: true` — the person's roles are not in this token. Reading them
 * as "no roles" would strip a person silently; this says so instead.
 */
export declare function rolesAreElsewhere(payload: Readonly<Record<string, unknown>>, rolesClaim: ClaimPath): boolean;
//# sourceMappingURL=personTest.d.ts.map