/**
 * Pack-declared rate limits — token-bucket with libsql persistence (AUTO.2).
 *
 * Authoritative source: `docs/tasks/automation.md` AUTO.2.
 *
 * Token-bucket over fixed-window because the trigger sources AUTO.2 feeds
 * (SCHED.1, AUTO.5, AUTO.6, SCHED.2) are bursty — a save firing 50
 * file_changed events in 1s then nothing for an hour should be allowed up
 * to `max` then metered, not denied "for the rest of the window".
 *
 * Math: continuous refill, clamped both sides.
 *   tokens = clamp(tokens + (now - last_refill_ms) * (max / per_ms), 0, max)
 * High clamp prevents float-creep above `max` over many partial refills;
 * low clamp guards against any future clock-skew drift below 0.
 *
 * Atomicity: explicit `BEGIN IMMEDIATE ... COMMIT` on the primary client
 * (not `client.transaction()`, which spins a separate logical connection
 * and starts from an empty `:memory:` db). SQLite's IMMEDIATE lock
 * serialises concurrent `check()`s at the storage layer — works the same
 * across daemon restarts (the survival requirement in the spec).
 *
 * Fail-closed (constraint C10): any libsql error returns `{allowed: false,
 * reason: 'rate_limit_storage_error'}` and invokes the caller's `onError`
 * sink. The limiter never silently allows traffic when storage is broken;
 * the caller routes the error to the user's notification channel.
 *
 * Time-source injection: `now: () => number = Date.now` on the
 * constructor; every test passes a fake clock.
 *
 * Imports from: @libsql/client.
 * Imported by: src/runtime/bootstrap.ts (wired by SCHED.1 + AUTO.6 later).
 */
import type { Client } from '@libsql/client';
import type { TriggerKind } from '../packs/schemas/skill.js';
/**
 * Per-trigger config; matches the `rate_limits:` manifest shape. `max` +
 * `per` required; `concurrent` defaults to no cap.
 */
export interface RateLimitConfig {
    max: number;
    per: 'minute' | 'hour' | 'day';
    concurrent?: number;
}
/**
 * Per-pack config: trigger kind → limit config. Absent key → unlimited
 * (spec: "Unconfigured packs are unlimited").
 */
export type PackRateLimits = Partial<Record<TriggerKind, RateLimitConfig>>;
export interface RateLimitDecision {
    allowed: boolean;
    /** Hint to callers — milliseconds until at least one token will be available. */
    retryAfterMs?: number;
    /**
     * Machine-readable cause when `allowed=false`. One of:
     *   - 'rate_exceeded'         — token bucket empty
     *   - 'concurrent_exceeded'   — concurrent counter at cap
     *   - 'rate_limit_storage_error' — libsql read/write failed (fail-closed)
     */
    reason?: 'rate_exceeded' | 'concurrent_exceeded' | 'rate_limit_storage_error';
}
export interface RateLimiterOpts {
    /** Per-pack limits; lookup miss → no limiter applied for that key. */
    limits: Map<string, PackRateLimits>;
    /** Injected clock — every test passes a fake `now`. */
    now?: () => number;
    /**
     * Optional storage-error sink. Caller routes to NotificationRouter; the
     * limiter never imports the channel stack directly (engine-vocabulary
     * discipline — speaks in pack/trigger/key only).
     */
    onError?: (err: unknown, ctx: {
        packId: string;
        triggerKind: TriggerKind;
        key: string;
    }) => void;
}
export declare class RateLimiter {
    private readonly db;
    private readonly limits;
    private readonly nowFn;
    private readonly onError?;
    private initialized;
    constructor(db: Client, opts: RateLimiterOpts);
    /**
     * Idempotent schema setup — `CREATE TABLE IF NOT EXISTS at first-use`,
     * matching the RAG backends. No versioned migration framework in
     * opensquid yet; idempotent DDL is the locked posture for AUTO.2.
     */
    init(): Promise<void>;
    /**
     * Request one slot. Allowed → decrement tokens, increment concurrent,
     * persist atomically. Denied (either cap or libsql error / fail-closed)
     * → `{allowed: false}` with a machine-readable `reason`.
     *
     * Unconfigured (pack, triggerKind) → `{allowed: true}` immediately, no
     * libsql touch (unlimited default per spec).
     */
    check(packId: string, triggerKind: TriggerKind, key: string): Promise<RateLimitDecision>;
    /**
     * Free the concurrent slot taken by a prior allowed `check()`. Floors at
     * 0 — over-releasing never drives the counter negative. Unconfigured
     * (pack, triggerKind) → no-op. libsql error → invoke `onError` (surface
     * to operator); fail-closed posture lives on `check()`, not `release()`.
     */
    release(packId: string, triggerKind: TriggerKind, key: string): Promise<void>;
    private readBucket;
    private writeBucket;
}
//# sourceMappingURL=rate_limit.d.ts.map