/**
 * Persistence for {@link AgentGrant} records.
 *
 * A single JSON file (`~/.mailpouch-agents.json`) holds every grant the
 * server has ever seen, across all three statuses. Writes are atomic via
 * tmp→rename, consistent with the rest of the project's credential-hygiene
 * story. The file is mode 0600.
 *
 * We deliberately keep this in memory as a Map<clientId, AgentGrant> and
 * flush the whole file on every mutation. n is small (<100 grants even in
 * aggressive use), JSON serialization is tens of μs, and atomic writes
 * eliminate the need for a lockfile.
 */
import type { AgentGrant, AgentGrantStatus, GrantConditions } from "./types.js";
import type { PermissionPreset, ToolName } from "../config/schema.js";
export interface CreatePendingArgs {
    clientId: string;
    clientName: string;
    /** IP the agent registered from (for display on the approval card). */
    registeredFromIp?: string;
}
export interface ApproveArgs {
    clientId: string;
    preset: PermissionPreset;
    toolOverrides?: Partial<Record<ToolName, boolean>>;
    conditions?: GrantConditions;
    note?: string;
}
export declare class AgentGrantStore {
    private grants;
    private readonly path;
    constructor(path: string);
    private load;
    /**
     * PERM-006: run a structural mutation under a cross-process advisory lock.
     * Both the MCP server and the settings server hold their own AgentGrantStore
     * instance over the same file; without locking + a reload-merge, one
     * process's whole-file rewrite silently drops grants the other process
     * created or modified. We reload the on-disk state under the lock, MERGE the
     * caller's pending in-memory record set on top (so a fresh local `set` made
     * by `fn` before persist isn't lost), apply `fn`, then persist atomically.
     */
    private mutate;
    /**
     * Reload the file from disk under the lock so on-disk state is authoritative
     * before we mutate + persist. This both recovers grants another process
     * created (the PERM-006 lost-grant case) AND refreshes the *status* of grants
     * we already hold — without this, a grant that process A approved/revoked
     * while we held a stale `pending` copy would be silently reverted by our
     * whole-file `persist()`. The one thing the disk is NOT authoritative for is
     * the call counters (`totalCalls`/`lastCallAt`): `recordCall` bumps those in
     * memory and defers the fsync, so if our in-memory count is ahead we carry it
     * forward onto the disk record rather than losing the unflushed increments.
     */
    private reloadMerge;
    private persist;
    /**
     * Record a brand-new DCR client as a pending grant. Called from the OAuth
     * DCR handler; idempotent when the same client_id is registered twice (e.g.
     * an MCP host retrying on a disconnected tunnel).
     */
    createPending(args: CreatePendingArgs): AgentGrant;
    /**
     * Ensure an *active* grant exists for a service account (client_credentials).
     * Unlike the interactive flow (createPending → approve), a service account is
     * pre-approved out-of-band at issuance, so its grant is born active. Called at
     * startup for each persisted service account; idempotent — refreshes the
     * preset/conditions/name on an existing grant and (re-)activates it, so an
     * operator re-issuing or editing an account converges the grant.
     */
    ensureActiveServiceGrant(args: {
        clientId: string;
        clientName: string;
        preset: PermissionPreset;
        conditions?: GrantConditions;
    }): AgentGrant;
    /**
     * Record live connection info captured at the MCP `initialize` handshake onto
     * an existing grant (display-only; does NOT change status or identity). No-op
     * if the clientId has no grant — an unregistered caller is handled by the
     * grant gate, not here.
     */
    recordConnection(clientId: string, info: {
        mcpClientName?: string;
        mcpClientVersion?: string;
        transport?: "http" | "stdio";
        registeredFromIp?: string;
    }): AgentGrant | null;
    approve(args: ApproveArgs): AgentGrant | null;
    deny(clientId: string, note?: string): AgentGrant | null;
    revoke(clientId: string): AgentGrant | null;
    /**
     * Mark a grant as expired in-place. Called by the gate when it observes
     * expiresAt has passed; atomic so concurrent tool calls agree on the state.
     */
    markExpired(clientId: string): AgentGrant | null;
    /**
     * Delete pending grants whose approval window (`maxAgeMs` since createdAt) has
     * elapsed — the auth request expires if the user doesn't approve in time, so
     * the agent must connect/auth again. Emits `grant-expired` for each (which
     * revokes any issued token) and removes the record. Returns the count expired.
     */
    expireStalePending(maxAgeMs: number): number;
    /** Record a successful tool call against the grant's counters. */
    recordCall(clientId: string): void;
    /** Force-flush any in-memory call-count updates to disk. */
    flushCounters(): void;
    get(clientId: string): AgentGrant | undefined;
    list(filter?: {
        status?: AgentGrantStatus;
    }): AgentGrant[];
    /** Drop revoked/expired grants older than `retainDays` days. */
    prune(retainDays?: number, now?: number): number;
}
//# sourceMappingURL=grant-store.d.ts.map