/**
 * Shared, consent-checked, allow-listed, timeout-bounded command executor.
 *
 * Every dashboard route that needs to spawn a local process must go through
 * this helper. Callers declare an explicit per-call-site allow-list; commands
 * not in that list are rejected synchronously without a spawn. Arguments are
 * passed positionally to `child_process.spawn` with `shell: false` so shell
 * metacharacters in `args` cannot be interpreted.
 *
 * Pitfalls:
 *   - Do NOT pass `shell: true`. Ever.
 *   - Do NOT accept user-supplied `env`. Callers must scrub secrets first.
 *   - Always use positional `args: string[]`, never a single command string.
 *   - The `allowList` is the security boundary, not `command -v`.
 */
import { type SpawnSyncReturns } from "node:child_process";
import { type EvidenceEnvelopeWriteOptions, type EvidenceEventKind } from "../evidence/envelope.js";
/** Spawn request accepted by `execSafely` after the caller has validated route inputs. */
export interface ExecOptions {
    /** The binary to spawn. Must exactly match an entry in `allowList`. */
    command: string;
    /** Positional argv after `command`. No shell interpolation; metacharacters
     *  in any arg cause a synchronous rejection before spawn. */
    args: string[];
    /** Working directory. Callers validate this with `validateLocalPath`. */
    cwd: string;
    /** Hard wall-clock cap; the process is killed (SIGTERM → SIGKILL) on expiry. */
    timeoutMs?: number;
    /** Explicit per-call-site whitelist. The command must be a member, regardless
     *  of whether `command -v` would resolve it. */
    allowList: readonly string[];
    /** Optional environment. Defaults to a minimal PATH/temp env; callers that
     *  pass env must scrub secrets first. */
    env?: Record<string, string>;
    /** Optional cap on captured stdout bytes. Defaults to 1 MB. */
    stdoutCapBytes?: number;
    /** Optional cap on captured stderr bytes. Defaults to 1 MB. */
    stderrCapBytes?: number;
    /** Optional local evidence event for spawned command completion. */
    evidence?: {
        projectPath: string;
        eventKind?: EvidenceEventKind;
        producer?: string;
        onWarning?: EvidenceEnvelopeWriteOptions["onWarning"];
    };
}
/** Stable result flags: `ok` means clean exit; `truncated` means an output cap fired. */
type ExecResultBooleanFields = Record<"ok" | "truncated", boolean>;
/** Completed process result with captured output bounded to the configured byte caps. */
export interface ExecResult extends ExecResultBooleanFields {
    /** Exit code; `null` if the process was killed by signal. */
    exitCode: number | null;
    /** Signal that terminated the process, if any. */
    signal: NodeJS.Signals | null;
    /** Captured stdout, truncated with a marker line if the cap fired. */
    stdout: string;
    /** Captured stderr, truncated with a marker line if the cap fired. */
    stderr: string;
    /** Whether the timeout fired. */
    timedOut: boolean;
    /** Wall-clock duration in milliseconds. */
    durationMs: number;
    /** Basename of the spawned command, for telemetry. */
    commandBasename: string;
}
/** Rejected safety check before any child process is spawned. */
declare class SafeExecRejection extends Error {
    readonly reason: "command-not-in-allow-list" | "args-contain-metacharacters" | "args-not-array";
    constructor(reason: "command-not-in-allow-list" | "args-contain-metacharacters" | "args-not-array", message: string);
}
export { SafeExecRejection };
/**
 * Write one file atomically inside a project root.
 *
 * The temp file lives beside the destination so `rename` stays atomic on the
 * same filesystem. Existing destination content is replaced only after the
 * temp file is flushed and closed.
 *
 * @param targetPath - destination path to replace atomically
 * @param content - complete file contents to write
 * @param projectRoot - project boundary that targetPath must stay within
 */
export declare function writeFileAtomic(targetPath: string, content: string, projectRoot: string): void;
/**
 * Spawns one allow-listed command without a shell and reports bounded output.
 *
 * The control flow stays explicit because each branch owns a different safety
 * invariant: pre-spawn rejection, timeout cleanup, output capping, spawn-error
 * recovery, and optional evidence writes.
 *
 * @param opts Spawn request plus allow-list, cwd, caps, and optional evidence settings.
 * @returns A promise that resolves with the process result or rejects with `SafeExecRejection`.
 */
export declare function execSafely(opts: ExecOptions): Promise<ExecResult>;
/**
 * Spawn request accepted by `spawnInheritedSync` for interactive CLI children.
 *
 * Contract: `allowedBasenames` matches the command's lowercased basename rather
 * than the full path, because interactive callers pass resolved absolute
 * binaries (for example a discovered Windows Git Bash path).
 */
export interface InheritedSpawnOptions {
    /** Resolved binary to spawn; its basename must appear in `allowedBasenames`. */
    command: string;
    /** Positional argv; rejected on shell metacharacters like `execSafely` args. */
    args: string[];
    /** Lowercase command basenames this call site permits (e.g. ["bash", "bash.exe"]). */
    allowedBasenames: readonly string[];
    /** Optional environment passed through to the child unchanged. */
    env?: NodeJS.ProcessEnv;
}
/**
 * Spawn an allow-listed command with inherited stdio for interactive CLI flows.
 *
 * Unlike `execSafely`, output is not captured or capped: stdin/stdout/stderr stay
 * attached to the caller's terminal, which suits long-running interactive children
 * such as the bundled installer. The same pre-spawn gates apply - basename
 * allow-list, metacharacter-free string args - and the child always runs with
 * `shell: false`. Throws `SafeExecRejection` before any process is spawned when a
 * gate fails.
 *
 * @param opts - command, argv, allowed basenames, and optional child environment
 * @returns the raw `spawnSync` result; callers read `status`, `signal`, and `error`
 */
export declare function spawnInheritedSync(opts: InheritedSpawnOptions): SpawnSyncReturns<Buffer>;
/**
 * Build the canonical key for side-effectful API route allow-lists.
 *
 * @param method HTTP method as received from the server.
 * @param path Normalised route path.
 * @returns Uppercase-method route key used by exact-match allow-lists.
 */
export declare function sideEffectfulRouteKey(method: string, path: string): string;
//# sourceMappingURL=safe-exec.d.ts.map