/**
 * Reconcile engine for `agents apply`. The diff (`diffFleet`) is pure and unit-
 * tested; execution (`probeDevice`, `reconcileDevice`, `runFleetApply`) drives
 * the real fleet over SSH and is verified end-to-end against live devices.
 *
 * Flow per device: probe → install/upgrade agents-cli → add missing agents →
 * sync config → propagate login. Every step reuses an existing primitive
 * (`readyProbe`, `bootstrapAgentsCli`, `buildRemoteAgentsInvocation`, `sshExec`).
 */
import type { DeviceProfile } from '../devices/registry.js';
import { type RemoteBackend } from '../secrets/push.js';
import type { DeviceDesired, DeviceProbe, DeviceDiff, FleetAction, FleetPlan, AuthFilePayload } from './types.js';
/** Strip a version suffix from an agent spec: `claude@latest` -> `claude`. */
export declare function agentIdOf(spec: string): string;
/**
 * The explicit pinned version of a spec, or undefined for an id-level spec.
 * `claude@2.1.170` -> `2.1.170`; `claude`, `claude@latest`, `claude@oldest`, and
 * `claude@all` all return undefined (the label channels install-latest / are
 * expanded upstream, so they diff at id granularity, not per-version).
 */
export declare function pinnedVersion(spec: string): string | undefined;
/** True when any spec in the roster pins an explicit version (needs a version probe). */
export declare function rosterNeedsVersions(desired: DeviceDesired[]): boolean;
/**
 * Expand any `<agent>@all` spec into one pinned spec per version installed on the
 * source, so `--agent claude@all` replicates THIS machine's exact version set.
 * `versionsOf` returns the source's installed versions for an agent id. Every
 * other spec passes through unchanged; the result is de-duplicated in order.
 * Throws if `@all` names an agent with no installed versions here (nothing to
 * replicate — a clear misconfig, not a silent no-op).
 */
export declare function expandAllSpecs(specs: string[], versionsOf: (id: string) => string[]): string[];
/**
 * Parse `agents view --json` (the all-agents array form) into a map of agent id
 * -> installed version strings. Tolerant: returns undefined on any parse failure
 * so a version-pinned spec falls back to id-level presence rather than crashing.
 */
export declare function parseInstalledVersions(stdout: string): Record<string, string[]> | undefined;
/** Source-side auth availability, computed once from `snapshotAuth`. */
export interface SourceAuth {
    /** Agent ids the source has a readable, propagatable credential file for. */
    available: Set<string>;
    /** Agent ids whose source auth is device-bound (macOS keychain). */
    bound: Set<string>;
    /** The captured file payloads, keyed by agent. */
    filesByAgent: Map<string, AuthFilePayload[]>;
}
/**
 * Bundles `fleet apply` considers for push. The reserved file-backed `auth`
 * bundle is always included when it exists locally — it is the fleet-shared
 * setup-token store and must not wait on `--provision-secrets` / a manifest
 * list (PHNX-2371).
 */
export declare function fleetSecretsBundles(declared: string[] | undefined): string[];
export interface DiffContext {
    /** agents-cli version the source is on — the fleet target version. */
    targetCliVersion: string;
    sourceAuth: SourceAuth;
    /** Secrets-bundle names the profile declares. */
    secretsBundles?: string[];
    /** `--provision-secrets`. OFF by default: pushing a bundle moves credential
     *  VALUES to another machine, so it is opted into per invocation and never
     *  defaulted from the shared `agents.yaml` (RUSH-1968). */
    provisionSecrets?: boolean;
    /** Is this device's host key pinned? Injected so `decideSecretPush` stays pure
     *  and its refusals are testable against real known_hosts fixtures with no
     *  network. Absent = treated as unpinned, i.e. refuse. */
    isHostPinned?: (device: string) => boolean;
    /** `--force`: push a declared bundle even when the device already has it. */
    forceSecrets?: boolean;
}
/** Pure: desired vs probed -> per-device diff + flat action list. */
export declare function diffFleet(desired: DeviceDesired[], probes: Map<string, DeviceProbe>, ctx: DiffContext): FleetPlan;
/** Why a declared bundle is or is not pushed to one device. */
export interface SecretPushDecision {
    push: boolean;
    /** Where it would land on the remote. Only meaningful when `push`. */
    backend: RemoteBackend;
    /** Set when `push` is false — rendered as the `needs-secret` reminder. */
    reason: string;
}
/**
 * Decide whether `fleet apply` may push one declared bundle to one device.
 *
 * PURE — no ssh, no keychain, no filesystem beyond the injectable pin check — so
 * every branch is unit-testable with no live fleet. Three gates, in order, and
 * each REFUSAL still yields a `needs-secret` reminder rather than silence:
 *
 *   1. `--provision-secrets` must be set. Off by default, and deliberately a
 *      flag rather than an `agents.yaml` field: the manifest is shared, so a
 *      file-level default means someone else's `apply -y` ships credential
 *      values without deciding to (RUSH-1968's shape of accident).
 *   2. The device must be reachable — nothing to push to otherwise.
 *   3. The host key must be PINNED. This moves credential values to another
 *      machine, so it reuses the same bar `agents exec --copy-creds` already
 *      sets (EXEC-34): an unpinned device earns its pin through a normal
 *      `agents ssh <device>` first.
 *
 * Backend follows the platform, and this is the load-bearing default of the
 * whole feature: **file on Linux, keychain on macOS/Windows**. A headless Linux
 * box has no keychain (`lib/secrets/linux.ts`), and the file store there
 * auto-provisions its OWN machine-local key — so each box ends up with an
 * unshared at-rest key and NO passphrase is forwarded. That is the direct
 * alternative to the fleet-wide shared secret this ticket exists to remove.
 *
 * The reserved `auth` bundle is the exception: it is always file-backed
 * (SEC-GAP-3) and is pushed even without `--provision-secrets`, because it is
 * the one fleet-shared setup-token store (PHNX-2371).
 */
export declare function decideSecretPush(bundle: string, desired: DeviceDesired, probe: DeviceProbe, ctx: DiffContext): SecretPushDecision;
export interface ProbeOptions {
    /** Also fetch per-agent installed versions (one extra `agents view --json`
     * round-trip). Enable only when the plan has a version-pinned spec. */
    withVersions?: boolean;
    /** Also fetch which secrets bundles the device already has (one extra
     *  `agents secrets list --json`). Enable only when the manifest declares
     *  bundles and provisioning is on — same cost discipline as `withVersions`. */
    withSecrets?: boolean;
}
/** Probe one device: reachability + agents-cli version + installed agent ids
 * (and, when `withVersions`, the installed version strings per agent). */
export declare function probeDevice(device: DeviceProfile, opts?: ProbeOptions): DeviceProbe;
/**
 * Narrow a remote `secrets list --json` payload to `name -> updated_at`.
 *
 * Exported and pure so the parse is unit-tested against real payload shapes with
 * no live fleet. Returns `{}` rather than throwing on anything unexpected: the
 * remote runs its own agents-cli version, and a parse failure must degrade to
 * "unknown, so push" — never to "present, so skip", which would silently leave a
 * device unprovisioned.
 */
export declare function parseRemoteBundles(stdout: string): Record<string, string>;
export interface ApplyStep {
    kind: FleetAction['kind'];
    ok: boolean;
    detail: string;
}
export interface DeviceApplyResult {
    device: string;
    ok: boolean;
    steps: ApplyStep[];
    note?: string;
}
export interface ExecContext {
    targetCliVersion: string;
    source: string;
    sourceAuth: SourceAuth;
    /** Set for a dry run — probe + plan only, execute nothing. */
    dryRun?: boolean;
}
/** Execute one device's planned actions in order. Real SSH — no mocks. */
export declare function reconcileDevice(row: DeviceDiff, device: DeviceProfile, ctx: ExecContext): DeviceApplyResult;
/** Run a pool of async tasks with a concurrency cap, preserving input order. */
export declare function pool<T, R>(items: T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
/** Reconcile every device row in parallel (capped). */
export declare function runFleetApply(rows: DeviceDiff[], nameToProfile: Map<string, DeviceProfile>, ctx: ExecContext, concurrency?: number): Promise<DeviceApplyResult[]>;
/** Default home for source snapshots (overridable in tests). */
export declare function sourceHome(): string;
