/**
 * Firecracker scaler backend implementation.
 *
 * Manages ephemeral agent microVMs using the Firecracker VMM via the jailer.
 * Handles full VM lifecycle: prepare chroot, hardlink rootfs+kernel, create overlay drive,
 * invoke jailer, inject MMDS metadata, and destroy with cleanup.
 *
 * Log capture: Firecracker backend handles log forwarding internally.
 * The jailer is spawned as a non-daemonized detached child with stdout redirected to a
 * serial console log file and VMM logs captured via --log-path. Both files are tailed
 * using fs.watchFile() and forwarded with distinct logsSource tags ('firecracker-serial',
 * 'firecracker-vmm'). The serial-console path is best-effort only: Firecracker's Rust
 * BufWriter around stdout + jailer's uid drop make sparse userspace writes unreliable,
 * so it's treated as defense-in-depth. The canonical agent log path is WS agent.log
 * (attached unconditionally by the agent; see packages/agent/src/server.ts step 6).
 *
 * Follows the Docker backend pattern closely for consistency.
 */
import { type ToolRequirement } from '@kici-dev/shared';
import type { IpAllocator, IpAllocationResult } from './ip-allocator.js';
import type { AgentTokenStore } from '../agent/token-store.js';
import type { ScalerBackend, ScalerEntry, ManagedAgent, LabelSetConfig, ScalerEventCallback, ValidationResult, EffectiveLimits } from './types.js';
/**
 * Infrastructure interfaces that share the `kici-` prefix but must NEVER be
 * deleted by the orphan sweep. Bridges (kici-br0/kici-br1) carry all VM
 * traffic; kici-m01 is the staging metadata interface. Losing any of these
 * kills all running VMs.
 */
export declare const PROTECTED_INTERFACES: readonly string[];
/** Extended ManagedAgent that includes the allocated IP for cleanup */
export interface FirecrackerManagedAgent extends ManagedAgent {
    /** Allocated IP address for TAP/IP release during destroy */
    ip: string;
    /** TAP device name for cleanup */
    tapDevice: string;
}
export interface FirecrackerScalerBackendOptions {
    /** Human-readable name for this scaler */
    name: string;
    /** Label sets this backend can provision */
    labelSets: LabelSetConfig[];
    /** Maximum concurrent agents */
    maxAgents: number;
    /** Shared IP allocator instance */
    ipAllocator: IpAllocator;
    /** Path to firecracker binary */
    firecrackerPath: string;
    /** Path to jailer binary */
    jailerPath: string;
    /** Default kernel image path */
    kernelPath: string;
    /** Jailer chroot base directory @default '/srv/jailer' */
    chrootBaseDir?: string;
    /** Jailer uid */
    uid: number;
    /** Jailer gid */
    gid: number;
    /** Default vCPU count @default 2 */
    vcpuCount?: number;
    /** Default memory in MiB @default 512 */
    memSizeMib?: number;
    /** Bridge interface name for TAP attachment */
    bridgeName: string;
    /** Gateway IP for guest networking */
    gateway: string;
    /** Netmask for guest networking */
    netmask: string;
    /** Token store for creating ephemeral agent auth tokens. Optional -- when undefined, no token is injected. */
    tokenStore?: AgentTokenStore;
    /** TTL for ephemeral agent tokens in ms. Default: 1 hour. */
    tokenTtlMs?: number;
    /** Agent roles for this scaler. undefined = all, [] = execution only. */
    roles?: string[];
    /**
     * Wrap privileged commands (`ip`, `chown`) with `sudo -n`. Required when the
     * orchestrator runs as a non-root user (e.g. user-mode systemd on edge worker
     * nodes) and operators have set up a sudoers NOPASSWD rule for those binaries.
     * On hosts where the orchestrator already runs as root, leave this false.
     * @default false
     */
    requireSudo?: boolean;
}
export declare class FirecrackerScalerBackend implements ScalerBackend {
    readonly type: "firecracker";
    readonly spawnsOnLocalHost = true;
    readonly maxAgents: number;
    readonly logsSource = "firecracker-serial";
    /** AbortControllers for file tailing per managed VM (keyed by agent ID) */
    private readonly tailAbortControllers;
    private _labelSets;
    private readonly name;
    private readonly ipAllocator;
    private readonly firecrackerPath;
    private readonly jailerPath;
    private readonly kernelPath;
    private readonly chrootBaseDir;
    private readonly uid;
    private readonly gid;
    private readonly vcpuCount;
    private readonly memSizeMib;
    private readonly bridgeName;
    private readonly gateway;
    private readonly netmask;
    private readonly tokenStore?;
    private readonly tokenTtlMs;
    private readonly roles;
    private readonly requireSudo;
    /** Tracks all managed VM agents by ManagedAgent.id */
    private readonly agents;
    /** Periodic orphan sweep timer (null when stopped). */
    private orphanSweepTimer;
    /** Guard against re-entrant sweeps if a single run takes longer than the interval. */
    private orphanSweepInFlight;
    constructor(options: FirecrackerScalerBackendOptions);
    /**
     * Declare required tools for a firecracker scaler entry.
     */
    static getRequiredTools(entry: ScalerEntry): ToolRequirement[];
    get labelSets(): LabelSetConfig[];
    getActiveCount(): number;
    spawn(labelSet: string[], agentId: string, orchestratorUrl: string, onEvent?: ScalerEventCallback, effectiveLimits?: EffectiveLimits): Promise<ManagedAgent>;
    getScalerContext(agentId: string): Record<string, unknown> | undefined;
    destroy(managedId: string): Promise<void>;
    shutdownAll(): Promise<void>;
    /**
     * Clear MMDS data for an agent after receiving config.ack via WS.
     * Belt-and-suspenders: even though MMDS only contains orchestrator URL,
     * clearing it reduces the attack surface to zero post-startup.
     *
     * Note: Wiring from agent-handler is done in Plan 04.
     *
     * @param agentId - The agent ID whose VM MMDS should be cleared
     */
    clearAgentMmds(agentId: string): Promise<void>;
    reload(labelSets: LabelSetConfig[]): ValidationResult;
    /**
     * Clean up orphaned VMs.
     *
     * Safe to call both at startup AND periodically while the backend is active
     * (see startPeriodicOrphanSweep): each pass explicitly skips VMs that are
     * currently tracked in-memory (spawning / running / destroying), and Pass 3
     * re-reads DB allocations after listing host interfaces to close the spawn
     * race (allocate → ip tuntap add is not atomic).
     *
     * Three passes:
     * 1. DB allocations: check if VM process is still running, clean dead ones
     * 2. Filesystem: scan chroot dir for directories not in DB allocations
     * 3. Network: scan host interfaces for orphan TAP devices
     *
     * Returns the count of cleaned orphans.
     */
    cleanupOrphans(): Promise<number>;
    /**
     * Default interval for periodic orphan sweeps (15 minutes).
     *
     * Long-running orchestrators need in-process orphan sweeps because the
     * external kici-leak-sweep timer skips TAP cleanup while a staging
     * orchestrator is active (to avoid racing spawns). Without this, leaked
     * TAPs accumulate until the orchestrator restarts — which can be weeks.
     */
    private static readonly ORPHAN_SWEEP_INTERVAL_MS;
    /**
     * Start a periodic orphan sweep. Idempotent — calling twice has no effect
     * beyond the first call. Use stopPeriodicOrphanSweep() to stop.
     *
     * @param intervalMs override the default interval (primarily for tests)
     */
    startPeriodicOrphanSweep(intervalMs?: number): void;
    /**
     * Stop the periodic orphan sweep started by startPeriodicOrphanSweep().
     * Safe to call when the sweep was never started.
     */
    stopPeriodicOrphanSweep(): void;
    /**
     * Build the merged forwarded-env map for an agent's MMDS payload.
     *
     * Mirrors bare-metal/container precedence: KICI_AGENT_ENV_*-prefixed vars from the
     * orchestrator's process.env (prefix stripped) are seeded first, then scalers.yaml
     * `env:` overlays them (yaml wins on conflict).
     *
     * Two safety filters apply:
     * - Keys must match POSIX_ENV_NAME_PATTERN. Otherwise rejected to keep MMDS path
     *   construction safe inside the VM and to prevent the guest from being asked to
     *   `export` something the shell can't represent.
     * - The cumulative byte cost (key + value + 2-byte overhead per entry) must stay
     *   under MMDS_FORWARDED_ENV_BUDGET_BYTES. Once exceeded, remaining entries are
     *   skipped and warnings logged. This protects the ~51 KiB MMDS data store cap
     *   from being blown by an accidentally-huge env value.
     */
    buildForwardedEnv(matchedLabelSet: LabelSetConfig, agentId: string): Record<string, string>;
    /**
     * Build the Firecracker VM configuration JSON.
     *
     * Resolution order for vcpu_count and mem_size_mib:
     *   1. effectiveLimits (resolved by ScalerManager from job/scaler/global caps) — wins if set.
     *   2. label-set's vcpuCount/memSizeMib.
     *   3. backend-level default vcpuCount/memSizeMib.
     *
     * Firecracker requires integer vCPUs and integer MiB. We round CPUs UP via
     * Math.ceil to never under-provision (a 0.5-CPU request still gets a whole vCPU)
     * and floor-divide bytes -> MiB. A debug log is emitted when rounding actually
     * changes the value, so operators can spot frequent fractional requests.
     */
    buildVmConfig(alloc: IpAllocationResult, labelSet: LabelSetConfig, effectiveLimits?: EffectiveLimits): Record<string, unknown>;
    /**
     * Create a sparse ext4 overlay drive for per-VM writable layer.
     * The file is sparse (allocates no disk blocks until written) and
     * pre-formatted as ext4 so the guest can mount it immediately.
     */
    private createOverlayDrive;
    /**
     * Promisified execFile wrapper with 30s timeout.
     */
    private execAsync;
    /**
     * Remove a VM's jailer chroot directory.
     *
     * On rootless nodes (requireSudo), the jailer chowns the chroot contents to
     * the jailer uid/gid, leaving the inner directories owned by that uid with
     * mode 0755 — so the orchestrator process (a different, non-root uid) has no
     * write permission on them and a plain `rm` fails with EACCES. destroy() and
     * both cleanupOrphans passes treat removal as best-effort and swallow that
     * failure, so each spawn would otherwise leak a multi-GiB chroot until the
     * data disk fills and the orchestrator crash-loops on ENOSPC (it can no
     * longer write its own files to start, so the in-process orphan sweep never
     * runs to recover). Reclaim ownership via the same sudo-wrapped `chown` path
     * used for `ip` before the `rm` runs as ourselves. On root nodes (requireSudo
     * false) the chown is skipped and `rm` works directly.
     */
    private removeChrootDir;
    /**
     * Get the API socket path for an agent's Firecracker VM.
     */
    getSocketPath(agentId: string): string;
    /**
     * Get the chroot directory path for an agent.
     */
    getChrootDir(agentId: string): string;
    /**
     * Check if a VM process is still alive by reading its PID file and sending signal 0.
     */
    private isVmProcessAlive;
    /**
     * Wait for a VM's jailer process to exit within a timeout.
     */
    private waitForProcessExit;
    /**
     * Clean up resources after a failed spawn attempt.
     */
    private cleanupFailedSpawn;
    /**
     * Start tailing serial console and VMM log files, forwarding lines directly
     * via forwardLine() with distinct logsSource tags.
     *
     * Also monitors the jailer child process for early exit (boot failure detection).
     * Forwarding is handled internally (not through ScalerManager/AgentLogForwarder)
     * because Firecracker has two distinct log sources per VM.
     */
    private startLogTailing;
    /**
     * Tail a single log file and forward each line via forwardLine().
     * For serial console lines: non-JSON lines (kernel boot output) are wrapped
     * as debug-level JSON; failure patterns (kernel panic, init failure) are elevated to error.
     */
    private tailAndForward;
    /**
     * Check if a serial console line matches known boot failure patterns.
     */
    private isFailurePattern;
    /**
     * Read the serial console log file and detect the type of boot failure.
     */
    private detectFailureType;
    /**
     * Read and forward any remaining log file content for an orphaned/dead VM.
     * Called during cleanupOrphans() before deleting the chroot directory.
     */
    private forwardRemainingLogs;
}
//# sourceMappingURL=firecracker-backend.d.ts.map