/**
 * hosting/signin/limits — attempt limits on a password door, correct under
 * concurrency, bounded, and per process.
 *
 * ── Why the order matters (review idI34 B-1) ────────────────────────────────
 * An attempt is COUNTED WHEN IT STARTS (`begin`), before the slow password
 * check — never after it. Counting after let forty parallel guesses all read
 * the same stale counter and all reach the checker. With `directory-password`
 * that burst would walk past this budget into Active Directory's own lockout
 * threshold and lock the real account company-wide; counting at the start is
 * what makes a budget "half of AD's threshold" mean anything.
 *
 * Three rules, then:
 *  1. **Per typed name: a hard budget.** `perName` attempts per window; past it
 *     the name is refused (429, `Retry-After`) until the window passes. A delay
 *     grows with the attempts already counted BEFORE any refusal, because a hard
 *     refusal is itself a lockout anyone can trigger for a colleague by typing
 *     their name (the documented trade: that is the price of a name budget).
 *  2. **One check in flight per name.** A second attempt for a name whose check
 *     has not answered is refused at once (429) — parallelism buys nothing.
 *  3. **Per client address: a growing DELAY only, never a refusal.** Behind a
 *     proxy or a shared NAT every person can arrive from one address; a hard
 *     address budget would let anybody lock the whole company out of sign-in
 *     with a handful of wrong guesses. The per-name budget is what protects an
 *     account; the address delay slows one client spraying many names. IPv6
 *     clients are counted per /64 (one customer's network), so rotating
 *     addresses inside it does not escape the delay.
 *     **Opt-in hard refusal (`refuseAddressAfter`):** an install whose clients
 *     each arrive from their OWN address (no shared NAT or proxy in front, or
 *     `trustedProxies` set so each person's address is resolved) can ask for a
 *     refusal too: once an address holds that many failed attempts inside the
 *     window, it is refused (429) without a check until a full window passes
 *     since its last counted attempt. Refused attempts are not counted, so the
 *     refusal is bounded by one window. Off by default, for the NAT reason
 *     above.
 *
 * ── Bounded without being flushable ─────────────────────────────────────────
 * Name and address counters live in two SEPARATE bounded maps, so address churn
 * can never push a name counter out. When a map is full, the least recently used
 * counter that is NOT penalising (none in flight, and not delaying or refusing)
 * is dropped; a counter that still penalises is never dropped. If every counter
 * in the map is penalising, a NEW name or address is refused as `busy` (503) —
 * failing closed rather than forgetting a penalty.
 *
 * What it does not claim: it REDUCES guessing and lockout risk and never
 * claims to prevent lockout; the counters are per process (N replicas give
 * N budgets — the banner says so).
 */
export interface AttemptLimits {
    /** Attempts allowed per typed name per window before refusal. Default 5. */
    readonly perName?: number;
    /** Attempts per client address per window after which the address's delay is at its cap. Default 20. */
    readonly perAddress?: number;
    /**
     * OPT-IN: failed attempts per client address per window after which the
     * address is REFUSED (429), not only delayed. Unset (the default): never
     * refused — behind a shared NAT or proxy every person is one address, and a
     * hard address budget lets anybody lock everybody out. Set it only when each
     * person's own address reaches the door (`trustedProxies` set behind a
     * proxy). A right password never counts against an address.
     */
    readonly refuseAddressAfter?: number;
    /** The window, in minutes. Default 15. */
    readonly windowMinutes?: number;
    /** The first back-off step, in ms; each further attempt doubles it (capped at 8×). Default 1000. */
    readonly backoffMs?: number;
    /** The most counters kept per map (names, addresses). Default 10 000. */
    readonly maxEntries?: number;
}
/** A counted attempt, to be settled exactly once. */
export interface AttemptTicket {
    readonly name: string;
    readonly address: string;
}
/** What the limiter says about an attempt it has just COUNTED (or refused to count). */
export type AttemptVerdict = {
    readonly kind: 'allow';
    readonly delayMs: number;
    readonly ticket: AttemptTicket;
} | {
    readonly kind: 'refuse';
    readonly retryAfterSeconds: number;
    /** Which rule refused: the name's budget, a check in flight for it, or the opt-in address budget. */
    readonly why: 'name-budget' | 'name-in-flight' | 'address-budget';
} | {
    readonly kind: 'busy';
    readonly retryAfterSeconds: number;
};
export interface AttemptLimiter {
    /** Count an attempt as it starts. `allow` carries a ticket that MUST be settled. */
    begin(name: string, address: string, now: number): AttemptVerdict;
    /** The credential was wrong: the attempt stays counted. */
    failed(ticket: AttemptTicket, now: number): void;
    /** The credential was right: the name's counter is cleared, and the address is not charged. */
    succeeded(ticket: AttemptTicket): void;
    /** The check could not run (the directory is down): the attempt is un-counted. */
    abandoned(ticket: AttemptTicket): void;
    /** How many counters are kept, both maps — for a test or a banner. */
    readonly size: number;
}
export declare function attemptLimiter(limits?: AttemptLimits): AttemptLimiter;
/**
 * The address a budget is kept for: an IPv4 address as is; an IPv6 address by
 * its /64 (the first four groups) — one network, however many addresses it
 * rotates through.
 */
export declare function addressBucket(address: string): string;
