/**
 * Per-principal cross-namespace query-budget limiter (issue #565 PR 4/5).
 *
 * Detects and throttles bursts of recall-type operations that a principal
 * issues against namespaces *other than their own*. Thresholds come from the
 * memory-extraction threat model (`docs/security/memory-extraction-threat-model.md`
 * §6.2) and the ADAM baseline report (`docs/security/adam-baseline-2026-04.md`):
 * a T2-class same-namespace attacker plateaus at 61 queries in the published
 * baseline, so the default window is set well below that to force any
 * adaptive loop to noticeably slow down.
 *
 * Shape:
 * - Pure, in-process, per-principal sliding window. No persistence.
 * - Only cross-namespace reads count: a principal hitting only their own
 *   namespace is never throttled.
 * - The limiter is behind the `recallCrossNamespaceBudgetEnabled` feature
 *   flag (defaults to `false`) and is a no-op when disabled. This mirrors
 *   the canonical "new filter/transform needs an enabled check" pattern
 *   (see CLAUDE.md gotcha #30).
 *
 * The module has no side effects beyond incrementing its own counters, and
 * it does NOT take a clock dependency — callers pass the current epoch ms
 * (or let the default `Date.now()` do it) so tests can step time
 * deterministically.
 */
interface CrossNamespaceBudgetConfig {
    /** Feature flag. Defaults to false — a disabled limiter is always allow. */
    enabled?: boolean;
    /**
     * Rolling window size in milliseconds. Counts decay out of the window
     * as the clock advances. Default: 60_000 (1 minute).
     */
    windowMs?: number;
    /**
     * Soft cap. Once a principal has `softLimit` cross-namespace reads in the
     * window, the limiter *records* a warning on the decision but still
     * allows the call. Used by PR 5's anomaly detector to surface flags
     * without blocking. Default: 10.
     */
    softLimit?: number;
    /**
     * Hard cap. Once `hardLimit` is reached, the limiter denies the call.
     * Default: 30 — picked to be well below the T2 baseline of ~60 queries
     * at half-plateau, so an ADAM-style adaptive loop is throttled before it
     * meaningfully leaks.
     */
    hardLimit?: number;
}
declare const DEFAULT_CROSS_NAMESPACE_BUDGET: Required<CrossNamespaceBudgetConfig>;
/**
 * Why a call was denied / warned. Stable strings so callers can key log
 * lines and metrics on them.
 */
type BudgetDecisionReason = "allowed-same-namespace" | "allowed-no-limit" | "allowed-under-soft" | "warn-over-soft" | "deny-over-hard";
interface BudgetDecision {
    allowed: boolean;
    reason: BudgetDecisionReason;
    /** Cross-namespace reads by this principal currently in the window. */
    count: number;
    /** Active config snapshot at decision time. */
    limit: {
        softLimit: number;
        hardLimit: number;
        windowMs: number;
    };
}
/**
 * In-process cross-namespace budget limiter. Instantiate once per
 * orchestrator / access-service.
 *
 * Threadsafe-by-construction: Node.js is single-threaded per process for
 * application code, and the limiter never awaits between read-modify-write
 * operations on its internal state.
 */
declare class CrossNamespaceBudget {
    private readonly config;
    private readonly buckets;
    constructor(config?: CrossNamespaceBudgetConfig);
    /** Exposed for tests / audit surfaces. Never mutate the returned value. */
    getConfig(): Required<CrossNamespaceBudgetConfig>;
    /**
     * Check whether `principal` is allowed to issue another cross-namespace
     * read. Call site is expected to compare `principalNamespace` against
     * `queryNamespace` and only pass through reads where they differ — the
     * limiter treats every call as a cross-namespace event.
     *
     * @param principal Stable identifier for the calling principal (token
     *   subject, session principal, etc.). Must be non-empty.
     * @param now Epoch-ms clock read. Defaults to `Date.now()`; tests pass a
     *   fixed value to step time deterministically.
     */
    record(principal: string, now?: number): BudgetDecision;
    /**
     * Read-only peek at whether a call would be allowed, WITHOUT recording a
     * timestamp. Useful when the caller must inspect multiple namespaces before
     * deciding to record a single event. The returned `count` reflects the
     * current window state at call time.
     */
    peek(args: {
        principal: string;
        principalNamespace: string;
        queryNamespace: string;
        now?: number;
    }): BudgetDecision;
    /**
     * Convenience guard that also skips the limiter when `principalNamespace`
     * equals `queryNamespace` (same-namespace is never cross-namespace).
     * Returns an `allowed-same-namespace` decision in that case.
     */
    check(args: {
        principal: string;
        principalNamespace: string;
        queryNamespace: string;
        now?: number;
    }): BudgetDecision;
    /**
     * Clear all state. Intended for tests and for the orchestrator's
     * lifecycle `before_reset` hook.
     */
    reset(): void;
    /**
     * Evict buckets whose entire timestamp list has slid out of the
     * active window by `now`. Intended to be called periodically by a
     * long-lived host process (e.g. from a maintenance cron) that sees
     * many transient principals. Safe to call at any time; returns the
     * number of buckets evicted.
     */
    gc(now?: number): number;
    /** For tests: current number of live buckets. */
    bucketCount(): number;
}

export { type BudgetDecision, type BudgetDecisionReason, CrossNamespaceBudget, type CrossNamespaceBudgetConfig, DEFAULT_CROSS_NAMESPACE_BUDGET };
