/**
 * The secrets-agent: an in-memory broker that holds resolved bundle env after one
 * Touch ID unlock, so concurrent agents don't each prompt.
 *
 * Security model: while unlocked, any same-user process reaching the socket can
 * read it silently — the same trust boundary the keychain already concedes. We
 * bound it with per-bundle opt-in, a TTL (~7d), auto-wipe on sleep/logout, and
 * explicit lock. Nothing touches disk. Off-darwin the broker is unused.
 */
import * as net from 'net';
import type { SecretsBundle } from './bundles.js';
import { GLOBAL_HARNESS, bundleScopeChain } from './scope.js';
import { type SecretLease } from './lease.js';
export { GLOBAL_HARNESS, bundleScopeChain };
/** Default lifetime of an unlocked bundle when `--ttl` is not given. */
export declare const DEFAULT_TTL_MS: number;
/**
 * Whether the secrets broker service is enabled in daemon service config.
 */
export declare function isSecretsBrokerEnabled(): boolean;
/**
 * Reserved store-key prefix for the `secrets list` metadata snapshot cache.
 * Keyed by a hash of the keychain name-set; adding/removing/renaming a bundle
 * changes the key and invalidates passively. The '!' sentinel cannot collide
 * with a real bundle name and is safe as argv.
 */
export declare const META_CACHE_PREFIX = "!meta:";
export { SYNC_GET_CMD, SYNC_PING_CMD, SYNC_LOCK_CMD } from './sync-commands.js';
/**
 * Decide whether a persistent broker should exit so launchd relaunches it on
 * freshly-installed code. Only when the store is empty: exiting with held
 * bundles would force a re-prompt. Deferring protects against rapid upgrades
 * wiping the hot cache (#435).
 */
export declare function shouldSelfHealForUpgrade(persistent: boolean, storeSize: number, runningVersion: string, onDiskVersion: string): boolean;
/**
 * Client-side twin: may only tear down a version-skewed broker when it holds
 * no real unlocks, otherwise every held bundle re-prompts (#435).
 */
export declare function shouldTeardownVersionSkewedBroker(realHeldBundles: number): boolean;
/**
 * Whether a client may evict a version-skewed broker. A daemon-hosted broker
 * must never be client-evicted: the daemon owns the socket via ownerPath(), so
 * unlinking it would orphan the broker until daemon restart (#435).
 */
export declare function shouldClientEvictSkewedBroker(daemonRunning: boolean, realHeldBundles: number): boolean;
export interface StoredBundle {
    bundle: SecretsBundle;
    env: Record<string, string>;
    /** epoch ms; the entry is gone once Date.now() passes this. */
    expiresAt: number;
    harness: string;
    lease?: SecretLease;
}
/** One unlocked bundle as reported by `status`. */
export interface AgentStatusEntry {
    name: string;
    expiresAt: number;
    keyCount: number;
    harness: string;
    leaseId?: string;
    keys?: string[];
}
/** Public accessor for the broker's socket path — `agents daemon status`/`services` reads it for display. */
export declare function secretsBrokerSocketPath(): string;
/**
 * Read the current broker capability token. A restart mints a new token, so a
 * stale read fails soft and the caller falls back to a direct keychain read.
 */
export declare function readAgentToken(): string | null;
/** True if a legacy standalone-broker launchd plist is still installed. */
export declare function secretsAgentServiceInstalled(): boolean;
/**
 * Retire the legacy standalone launchd service so the daemon owns the socket.
 * Idempotent; does not wipe held bundles.
 */
export declare function retireLegacySecretsAgentService(): void;
/**
 * Stop the persistent broker for `agents secrets stop`: wipe held bundles, then
 * retire any legacy standalone service. The daemon-hosted broker itself is left
 * running because it backs unrelated background work.
 */
export declare function uninstallSecretsAgentService(): Promise<void>;
export type Request = {
    cmd: 'ping';
} | {
    cmd: 'get';
    name: string;
    harness?: string;
    token?: string;
} | {
    cmd: 'load';
    name: string;
    harness?: string;
    bundle: SecretsBundle;
    env: Record<string, string>;
    ttlMs: number;
    lease?: SecretLease;
    token?: string;
    snapshotAt?: number;
} | {
    cmd: 'lock';
    name?: string;
    token?: string;
} | {
    cmd: 'revoke';
    leaseId: string;
    token?: string;
} | {
    cmd: 'status';
    token?: string;
};
export type Response = {
    ok: true;
    cmd: 'ping';
    version: number;
    cliVersion: string;
} | {
    ok: true;
    cmd: 'get';
    hit: false;
} | {
    ok: true;
    cmd: 'get';
    hit: true;
    bundle: SecretsBundle;
    env: Record<string, string>;
    lease?: SecretLease;
} | {
    ok: true;
    cmd: 'load';
} | {
    ok: true;
    cmd: 'lock';
    wiped: number;
} | {
    ok: true;
    cmd: 'revoke';
    wiped: number;
} | {
    ok: true;
    cmd: 'status';
    entries: AgentStatusEntry[];
} | {
    ok: false;
    error: string;
};
/**
 * Count of real unlocked bundles, excluding the internal `secrets list` metadata
 * cache. A metadata-only store must read as empty so it doesn't block upgrade
 * self-heal or idle-exit (#435). Exported for tests.
 */
export declare function realBundleCount(store: Map<string, StoredBundle>): number;
export declare function scopedBundleKey(name: string, harness: string): string;
/** Pure request handler over the in-memory store. Exported for tests. */
export declare function handleAgentRequest(store: Map<string, StoredBundle>, req: Request, now?: number, evictedAt?: Map<string, number>): Response;
/**
 * Wipe the in-memory store only on SLEEP, not screen-lock (already gated by the
 * login password). Exported for regression coverage of the LOCK-survives /
 * SLEEP-wipes contract.
 */
export declare function shouldWipeOnWatchEvent(chunk: string): boolean;
/**
 * Authorization gate: every request except `ping` must carry the per-broker
 * capability token from the 0600 token file. `ping` stays unauthenticated so
 * clients can probe reachability before reading the token. Fail closed. Exported
 * for tests.
 */
export declare function isRequestAuthorized(req: Request, expectedToken: string | null): boolean;
type BrokerConnectionHandler = (conn: net.Socket) => void;
/**
 * Build the socket `connection` handler shared by standalone and daemon-hosted
 * brokers. Newline-framed JSON in, one response line out, with per-request token
 * lookup so rotation on restart is picked up.
 */
export declare function makeConnectionHandler(handle: (req: Request) => Response, token: () => string | null): BrokerConnectionHandler;
/**
 * Whether a live process still owns the broker pid file. A single missed ping
 * isn't proof of death: the single-threaded broker can blow the ping budget
 * while healthy. The pid file is the second liveness signal that makes reclaim
 * safe.
 */
/** Drop our ownership record only if it is still ours. */
export declare function releaseBrokerPid(): void;
export declare function brokerPidAlive(): boolean;
/**
 * Run the standalone broker in the foreground. Spawned by ensureAgentRunning via
 * `agents secrets _agent-run`. Serves the socket, sweeps expired entries, wipes
 * on sleep, and self-exits when idle.
 */
export declare function runSecretsAgent(opts?: {
    service?: boolean;
}): Promise<{
    close(): void | Promise<void>;
} | null>;
/**
 * Host the secrets broker inside the always-on daemon (#416). Serves the same
 * socket/protocol as the standalone broker, but daemon-safe: no pid-file guard,
 * no process.exit/SIG handlers, no self-heal/idle-exit (the daemon owns the
 * lifecycle). Returns null off-darwin.
 */
export declare function startHostedBroker(): Promise<{
    close(): void | Promise<void>;
} | null>;
/**
 * Wait for net.Server.close()'s 'close' event (or a bounded timeout) so a
 * successor bind doesn't race a half-closed socket (RUSH-2421).
 */
export declare function closeServerBounded(server: net.Server, timeoutMs?: number): Promise<void>;
/** Cheap socket-existence check so the never-unlocked path stays a single stat. */
export declare function agentSocketExists(): boolean;
/**
 * Spawn one of the `__secrets-*` sync clients via `getCliLaunch`. The old
 * `process.execPath -e` pattern broke on the bun-compiled Mach-O binary
 * (1.20.53); routing through getCliLaunch fixes both install shapes. The
 * subcommands are intercepted in index.ts before commander/startup so a cache
 * hit doesn't fork detached syncs or run update checks.
 */
export declare function syncClientLaunch(sub: string[], agentsBin?: string): {
    command: string;
    args: string[];
};
/**
 * Synchronous read for the hot path. Returns null on any failure so the caller
 * falls through to the real keychain. macOS only.
 */
export declare function agentGetSync(name: string, harness?: string): {
    bundle: SecretsBundle;
    env: Record<string, string>;
    lease?: SecretLease;
} | null;
/**
 * Last non-empty line of a child's stdout — the payload line. Anchors on the
 * terminator so any future CLI startup chatter on stdout doesn't break JSON.parse
 * and silently turn cache hits into misses.
 */
export declare function lastLine(stdout: string): string;
/**
 * Synchronous liveness check: is a broker actually listening? Gates the
 * synchronous warm path so a stale socket doesn't drag a foreground read
 * through a cold-start. macOS only.
 */
export declare function agentReachableSync(): boolean;
/**
 * Synchronously evict one bundle after a mutating write so the broker doesn't
 * serve a stale snapshot for the hold window. Best-effort silent no-op. macOS only.
 */
export declare function agentEvictSync(name: string): void;
/** Body of `__secrets-get <name>`. Exit 0 = hit, 3 = miss/down. */
export declare function runAgentGetSync(name: string, harness?: string): Promise<number>;
/** Body of `__secrets-ping`. Exit 0 = listening broker, 3 = nothing there.
 * Does not gate on PROTOCOL_VERSION so version-skewed brokers still answer reads. */
export declare function runAgentPingSync(): Promise<number>;
/** Body of `__secrets-lock <name>`. Best-effort evict; exit 0 even when no
 * broker answers. An empty name is refused to avoid accidentally locking ALL
 * bundles. */
export declare function runAgentLockSync(name: string): Promise<number>;
/**
 * Read the cached `secrets list` metadata snapshot for a keychain name-set hash.
 * Reuses agentGetSync. The name-set hash is the cache key, so add/remove/rename
 * yields a clean miss.
 */
export declare function agentGetMetaSync(nameSetHash: string): SecretsBundle[] | null;
/**
 * Fire-and-forget: populate the broker with a metadata snapshot so the next
 * `secrets list` within the hold window is prompt-free. Stored under a reserved
 * META_CACHE_PREFIX key; snapshot travels over stdin. macOS only.
 */
export declare function agentAutoLoadMetaSync(nameSetHash: string, bundles: SecretsBundle[], ttlMs: number): void;
/** True unless `secrets.agent.auto` is explicitly disabled in agents.yaml. The
 * broker is the mechanism that delivers the `daily` default policy (one Touch ID
 * per ~7d), so auto-caching is ON by default; opt out with
 * `secrets.agent.auto: false`. Best-effort; an unreadable meta reads as on. */
export declare function secretsAgentAutoEnabled(): boolean;
/** Default `sleepPersist` for a new unlock when `--durable` is not passed. OFF by
 * default (the secure split default: survive restart, re-lock on sleep); set
 * `secrets.agent.durable: true` in agents.yaml to make every unlock sleep-durable.
 * Best-effort; an unreadable meta reads as off. */
export declare function secretsAgentDurable(): boolean;
/** Minimum / maximum bounds for the configurable hold window. A too-small value
 * would defeat the broker (constant re-prompts); a too-large one pins secrets in
 * memory far longer than intended. */
export declare const MIN_HOLD_MS: number;
export declare const MAX_HOLD_MS: number;
/**
 * How long an unlocked / auto-cached bundle is held before the next read
 * re-prompts. Defaults to DEFAULT_TTL_MS (7d); override with
 * `secrets.agent.holdMs` (milliseconds) in agents.yaml — e.g. 86400000 for a 24h
 * cap. Clamped to [MIN_HOLD_MS, MAX_HOLD_MS] so a typo can neither disable the
 * hold nor pin a secret in memory indefinitely. Best-effort: an unreadable or
 * non-numeric value falls back to the 7d default. Pure except for the meta read.
 */
export declare function secretsHoldMs(): number;
/** Pure clamp for a configured `holdMs`: a positive finite number is bounded to
 * [MIN_HOLD_MS, MAX_HOLD_MS]; anything else (absent, 0, negative, NaN, non-number)
 * falls back to the 7d default. Exported for direct unit testing. */
export declare function clampHoldMs(v: unknown): number;
/**
 * Fire-and-forget: populate the broker with a freshly-resolved bundle so the
 * NEXT process reads it without a prompt. Used by the auto-cache path after a
 * real keychain read of a `daily`-policy bundle, so the NEXT concurrent read is
 * silent. Env travels over stdin, never argv.
 *
 * Reliability (this is what makes `daily` actually "stick"): when a broker is
 * ALREADY listening, warm it SYNCHRONOUSLY with a bounded wait so the bundle is
 * held by the time this process exits. The old detached-only path lost the race
 * under load — a short-lived reader (`agents secrets export`, a release-script
 * loop) exited before the unref'd worker connected, so the cache silently never
 * populated and every read re-prompted despite the `daily` policy. Only when the
 * broker must COLD-START (no socket yet) do we fall back to the detached worker,
 * so a first-ever read never blocks on a multi-second broker boot.
 *
 * The worker reuses the robust `ensureAgentRunning` path (spawn-then-ping) rather
 * than a tight inline retry loop. Best-effort; never throws. macOS only.
 */
export declare function agentAutoLoadSync(name: string, bundle: SecretsBundle, env: Record<string, string>, ttlMs: number, harness?: string, lease?: SecretLease, snapshotAt?: number): void;
/**
 * Body of the hidden `secrets _agent-load` worker. Reads one `{name, bundle,
 * env, ttlMs}` payload from stdin, ensures the broker is up (robust, generous
 * budget), and loads the bundle into it.
 *
 * Exit code is load-truthful: 0 ONLY when the bundle was actually loaded into a
 * reachable broker; non-zero on any failure (malformed payload, broker couldn't
 * be brought up, or the load transport failed). The synchronous caller
 * (agentAutoLoadSync) relies on this to decide whether to skip the detached
 * fallback — a bare "process exited 0" would otherwise be a false-positive
 * success that silently reintroduces the very re-prompt storm this path fixes.
 */
export declare function runAgentLoadFromStdin(): Promise<void>;
/** Store a resolved bundle in the broker. Returns false on transport failure. */
export declare function agentLoad(name: string, bundle: SecretsBundle, env: Record<string, string>, ttlMs: number, harness?: string, lease?: SecretLease, snapshotAt?: number): Promise<boolean>;
/** Wipe one bundle (or all if name omitted) from the broker. Returns the count
 * wiped, or 0 when no broker is running. */
export declare function agentLock(name?: string): Promise<number>;
/** List currently-unlocked bundles, or [] when no broker is running. The
 * internal `secrets list` metadata-cache entry is filtered out here as well as
 * server-side: during a rollout a NEW client can talk to an OLD broker that
 * predates the server-side exclusion, so this keeps the internal entry from
 * surfacing in `agents secrets status` in that skew window. */
export declare function agentStatus(): Promise<AgentStatusEntry[]>;
/** Ping result: whether a broker is reachable + speaking our protocol, and the
 * version of the code it's running (for staleness detection). */
export declare function agentPing(): Promise<{
    reachable: boolean;
    cliVersion?: string;
}>;
/**
 * Ensure a broker is running and reachable. Returns true once the socket answers
 * a ping. macOS only.
 *
 * Prefers the always-on daemon, which hosts the broker socket (#416): retire any
 * legacy standalone launchd service so the daemon owns the socket, then bring the
 * daemon up (Path 0) — one supervised backbone that survives the whole login
 * session, so subsequent reads never cold-start. Only when the daemon can't be
 * used do we fall back to a one-off detached broker (Path 1) — the model that
 * gets starved under heavy load, so it's last.
 */
export declare function ensureAgentRunning(timeoutMs?: number): Promise<boolean>;
