/**
 * Agent grants — domain layer over the `agent_grant` entity_type.
 *
 * Grants are first-class entities (see Stronger AAuth Admission Plan) so
 * they ride the standard entity store: observations, snapshots,
 * `correct`, history view. This module is a thin wrapper that:
 *
 *   - validates the grant shape (capabilities, identity, status)
 *   - exposes ergonomic CRUD helpers reused by the REST routes,
 *     Inspector grants page, env-config import, and the AAuth
 *     admission service
 *   - keeps a small in-memory cache for identity → grant lookups so
 *     admission stays cheap on the hot path while still propagating
 *     revocation within a few seconds
 *
 * Design notes:
 *
 *   - Writes flow through `storeStructuredForApi` / `createCorrection`
 *     so observation history doubles as the audit log. We never insert
 *     observations directly here.
 *   - Cache is keyed by `(thumbprint | sub:iss | sub)` and invalidated
 *     after a small TTL plus on every grant write made through this
 *     service. Revocation made via Inspector / direct entity-store
 *     calls is picked up after at most one TTL cycle.
 *   - Cross-user identity matches: a grant is owned by exactly one
 *     `user_id` (entities row). When the same identity has grants
 *     under multiple users we pick the most recently observed active
 *     grant; admission then resolves to that grant's owner. The
 *     protected-entity-types guard plus per-grant capability rules
 *     keep an admitted agent locked to its owner.
 */
import { type AgentCapabilityEntry } from "./agent_capabilities.js";
export type AgentGrantStatus = "active" | "suspended" | "revoked";
/**
 * Identity match shape persisted on a grant. At least one of
 * `match_sub` or `match_thumbprint` MUST be present (validation lives
 * in {@link validateGrantDraft}). When `match_iss` is set, both `sub`
 * and `iss` from the request must match.
 */
export interface AgentGrantMatch {
    match_sub?: string | null;
    match_iss?: string | null;
    match_thumbprint?: string | null;
}
export interface AgentGrant extends AgentGrantMatch {
    grant_id: string;
    user_id: string;
    label: string;
    capabilities: AgentCapabilityEntry[];
    status: AgentGrantStatus;
    notes?: string | null;
    last_used_at?: string | null;
    import_source?: string | null;
    created_at?: string;
    last_observation_at?: string;
    /** Linked GitHub login (set by `neotoma github link` or Inspector OAuth). */
    linked_github_login?: string | null;
    /** Linked GitHub numeric user id. */
    linked_github_user_id?: number | null;
    /** Timestamp when GitHub link was verified via OAuth. */
    linked_github_verified_at?: string | null;
}
export interface AgentGrantDraft extends AgentGrantMatch {
    label: string;
    capabilities?: AgentCapabilityEntry[];
    status?: AgentGrantStatus;
    notes?: string | null;
    import_source?: string | null;
}
export interface AgentGrantUpdate {
    label?: string;
    capabilities?: AgentCapabilityEntry[];
    notes?: string | null;
    match_sub?: string | null;
    match_iss?: string | null;
    match_thumbprint?: string | null;
}
export interface ListGrantsFilters {
    status?: AgentGrantStatus | "all";
    /** Substring match against `label`, `match_sub`, or `match_thumbprint`. */
    query?: string;
}
export declare class AgentGrantValidationError extends Error {
    readonly code: "agent_grant_invalid";
    readonly statusCode = 400;
    readonly field?: string;
    constructor(message: string, field?: string);
}
export declare class AgentGrantNotFoundError extends Error {
    readonly code: "agent_grant_not_found";
    readonly statusCode = 404;
    readonly grantId: string;
    constructor(grantId: string);
}
export declare class AgentGrantStatusTransitionError extends Error {
    readonly code: "agent_grant_status_transition";
    readonly statusCode = 409;
    readonly from: AgentGrantStatus;
    readonly to: AgentGrantStatus;
    constructor(from: AgentGrantStatus, to: AgentGrantStatus);
}
/**
 * Validate the capabilities array. Returns a freshly-allocated, normalised
 * array so callers can pass the result straight to the entity store
 * without worrying about prototype pollution or extra fields.
 */
export declare function validateCapabilities(raw: unknown): AgentCapabilityEntry[];
/** Drop every cache entry that touches the given grant's identity. */
export declare function invalidateGrantCache(grant?: AgentGrant): void;
/** Test-only escape hatch. */
export declare function clearGrantCacheForTests(): void;
/**
 * List grants for a user, optionally filtered. Caller scope is assumed
 * already enforced (route-level auth).
 */
export declare function listGrantsForUser(userId: string, filters?: ListGrantsFilters): Promise<AgentGrant[]>;
export declare function getGrant(userId: string, grantId: string): Promise<AgentGrant | null>;
/**
 * Find the most recently-observed active grant matching the supplied
 * AAuth identity. Thumbprint match wins over `(sub, iss)` — see plan.
 * Returns `null` when nothing matches.
 */
export declare function findActiveGrantByIdentity(input: {
    sub?: string | null;
    iss?: string | null;
    thumbprint?: string | null;
}): Promise<AgentGrant | null>;
/**
 * Create a new grant for `userId`. Capability shape is validated.
 * Status defaults to `active`. Idempotent on the canonical-name key
 * derived from the identity match.
 */
export declare function createGrant(userId: string, draft: AgentGrantDraft): Promise<AgentGrant>;
/**
 * Update mutable grant fields. Each changed field is persisted as a
 * `correct` observation so observation history shows exactly what
 * moved. Invalid status transitions (use {@link setStatus}) and
 * identity changes that violate the at-least-one-match rule are
 * rejected up front.
 */
export declare function updateGrantFields(userId: string, grantId: string, changes: AgentGrantUpdate): Promise<AgentGrant>;
export declare function setStatus(userId: string, grantId: string, next: AgentGrantStatus): Promise<AgentGrant>;
/**
 * Record that the supplied grant was used for admission. Debounced to
 * once per UTC day per grant so admission cost on hot paths stays
 * bounded. Best-effort: failures are logged and swallowed because the
 * caller is on the request critical path.
 */
export declare function recordMatch(grant: AgentGrant): Promise<void>;
/** Test-only — clears the debounce map. */
export declare function clearMatchDebounceForTests(): void;
//# sourceMappingURL=agent_grants.d.ts.map