/**
 * fileRecordingSink — one archived run, one JSON file, in a directory.
 *
 * The destination that needs nothing installed: an incident can be inspected
 * with `ls` and `cat`, and a run archive is a folder you can tar. It is also
 * the reference implementation of {@link RecordingSink} — a sink is one method,
 * and this file is what "implement the other ones like this" points at.
 *
 * ## The file name is a key, and keys must be injective
 *
 * `runId` becomes a file name, which makes `runId ↦ name` a mapping used as a
 * key: two different runs landing on one name means one archive silently
 * overwrites another, and the evidence is gone with no error anywhere. So the
 * mapping is `runId + '.json'` — appending a constant suffix, which is
 * injective — over a DOMAIN that is asserted rather than assumed, and anything
 * outside it is refused by name.
 *
 * The domain is `[a-z0-9]` followed by `[a-z0-9._-]*`, and every exclusion is
 * load-bearing:
 *
 *   • **no uppercase.** This is the one that looks like fussiness and is not.
 *     macOS/APFS and Windows/NTFS are case-INSENSITIVE by default, so `run-A`
 *     and `run-a` are two distinct strings that name ONE file. A mapping that
 *     is injective as a string can still collide as a file name, which is
 *     exactly the bug artifacts/scopePath.ts found on a stock Mac in 9.44.0 —
 *     its conformance battery had pairs for separators, absence markers and
 *     pre-escaped values, and no pair differing only in case. Excluding
 *     uppercase from the domain means no two valid ids differ by case alone, so
 *     there is nothing for a case-folding filesystem to fold together.
 *   • **no `/` or `\`.** A separator inside a field is separator donation: an
 *     id containing one would silently become a path with a directory hop.
 *   • **no leading `.` or `-`.** A leading dot hides the archive from `ls`; a
 *     leading dash is read as a flag by every CLI tool that would then handle
 *     it. Both are enforced by the first-character class.
 *   • **no bare `.` / `..`.** Path navigation, not names.
 *   • **length capped.** `NAME_MAX` is 255 on the common filesystems and the
 *     suffixes here add to it.
 *   • **no Windows reserved device names.** `con`, `nul`, `com1` … are devices
 *     with OR without an extension: `con.json` opens the console, not a file.
 *
 * Ids this library mints all satisfy it: `makeRunId()` produces
 * `run-<epoch ms>-<seq>` and the footprintjs engine produces
 * `<epoch ms>-<padded counter>`. The assertion is there for the ids a CALLER
 * states, which is the case that is neither controlled nor rare.
 *
 * ## Atomic, so a reader never sees half an archive
 *
 * The bytes go to a temporary file in the same directory and are then renamed
 * into place. `rename` within one filesystem is atomic, so a crash mid-write
 * leaves a `.tmp` nobody reads rather than a truncated `.json` that parses as
 * far as it got — a half-written archive is the one failure a bug report cannot
 * survive, because it looks like evidence.
 *
 * Writing the same `runId` twice REPLACES the file, atomically. That is the
 * intended behaviour and worth stating: the run id is the archive's identity,
 * so a second envelope for one run is a newer version of one archive (a partial
 * crash dump later superseded by the finished run), not a second archive.
 *
 * Node-only. `node:fs` is reached through `lazyRequire`, the same law the other
 * filesystem adapters follow, so importing the door costs a browser bundle
 * nothing and constructing one where there is no filesystem refuses by name.
 */
import type { RecordingSink } from './recordingEnvelope.js';
/**
 * Raised when a run id cannot safely become a file name.
 *
 * Its own class because the fix is never a retry: the caller has to name the
 * archive something a filesystem can hold one-to-one.
 */
export declare class UnsafeRecordingIdError extends Error {
    readonly code: "ERR_UNSAFE_RECORDING_ID";
    readonly runId: string;
    constructor(runId: string, reason: string);
}
/**
 * The mapping under test: one run id → one file name, injectively.
 *
 * Exported so the collision battery can drive the mapping directly rather than
 * inferring it from files on a disk.
 *
 * @throws {UnsafeRecordingIdError} for any id outside the safe domain.
 */
export declare function recordingFileName(runId: string): string;
/** Options for {@link fileRecordingSink}. */
export interface FileRecordingSinkOptions {
    /** The archive directory. Created if missing, parents included. */
    readonly directory: string;
}
/**
 * A directory-backed recording sink — one JSON file per run, written atomically.
 *
 * @example
 * ```ts
 * const recorder = recordRun(agent);
 * await agent.run({ message: 'hi' });
 *
 * await persistRecording(recorder, {
 *   sink: fileRecordingSink({ directory: './run-archive' }),
 *   run: { complete: true },
 * });
 * // → ./run-archive/run-1787093273110-1.json
 * ```
 */
export declare function fileRecordingSink(options: FileRecordingSinkOptions): RecordingSink;
