/**
 * Parse + validate the `fleet:` block of a profile manifest and resolve it into
 * per-device desired state.
 *
 * `fleet:` is additive to the `Meta` schema (`types.ts`). Any `-f <file>` that
 * carries a `fleet:` block is a valid manifest, so `ag apply -f agents.yaml`
 * works against the project file. The functions here are pure (no SSH, no
 * registry I/O) so they're fully unit-testable; device enumeration is injected.
 */
import type { FleetManifest, DeviceDesired } from './types.js';
/** Validate a raw `fleet:` object (already YAML-parsed) into a typed manifest. */
export declare function parseFleetManifest(raw: unknown): FleetManifest;
/** Read a YAML file and extract + validate its `fleet:` block. */
export declare function readFleetFile(filePath: string): FleetManifest;
export interface ResolveContext {
    /** Device names currently online (used to expand `devices: all`). */
    onlineDevices: string[];
    /** All registered device names (used to validate explicit entries). */
    registeredDevices: string[];
    /** The source machine, always excluded from the target set. */
    source: string;
    /** Names the bootstrap could not resolve from Tailscale (off-tailnet, ignored,
     * or a typo). These are SKIPPED with the caller's warning rather than aborting
     * the whole reconcile — a manifest naming an asleep laptop must not hard-fail
     * every other device. Without a bootstrap, this is empty and an unregistered
     * name still throws (genuine misconfig, caught early). */
    unresolved?: string[];
}
/**
 * Expand a manifest into concrete per-device desired states. `devices: all`
 * expands to every online registered device minus the source; an explicit map
 * validates each name against the registry. Devices with no desired agents,
 * sync scopes, and `login: skip` are still returned (probe/report only).
 */
export declare function resolveDesired(manifest: FleetManifest, ctx: ResolveContext): DeviceDesired[];
/**
 * The message to print when a reconcile resolved zero target devices.
 *
 * An explicitly empty roster (`fleet.devices: {}` — the default on a fresh box)
 * is the ACTIONABLE case: the engine ran fine, there is simply nothing declared
 * to converge, so name the empty key and how to fill it. A gray "nothing to
 * apply" there reads like the command is dead — the exact confusion that made
 * `apply` look like a broken command during the RUSH-2981 surface review
 * (PHNX-3422). Any OTHER zero-target case (a `devices: all` fleet with no online
 * peers, or an explicit roster whose every name was unresolved — off-tailnet,
 * ignored, or a typo, the only names `resolveDesired` drops) already had its
 * reason surfaced above, so it stays a plain note.
 */
export declare function emptyTargetsMessage(manifest: FleetManifest): {
    style: 'hint' | 'plain';
    lines: string[];
};
