import type { ToolResultDeletionReport, ToolResultProvenance, ToolResultSlice, ToolResultStore } from "../../core/tool-result-store.js";
import { type CorruptReadInfo } from "./corrupt-read-sink.js";
/**
 * design/80 §2.5 — file {@link ToolResultStore}: write-once-idempotent, ONE file per ref.
 *
 * `ref = tr_<sessionId>~<toolCallId>` (engine-minted, already globally unique; the separator is the
 * backlog #119 injective form). `put` is an `O_CREAT|O_EXCL` create → on `EEXIST` it no-ops
 * (write-once-idempotent; never re-writes — a re-write risks a non-identical preview and breaks the
 * prompt cache, §2.5 / the in-memory store's `map.has(ref)` check). Content is fsync'd before return.
 * `get` slices the file by `{offset, limit}` over CHARS to match `ToolResultSlice.totalChars` semantics;
 * an unknown ref → `undefined` (the model is told it's gone — degrades, never crashes).
 * `withToolResultOffload` joins text with `\n` and stores the joined string verbatim, so the char-offset
 * slice here is exactly the in-memory store's slice.
 *
 * Provenance (#119) lives in a SIDECAR file next to the content one (`<encoded>.owner.json`), not in a
 * header inside it: the content file's bytes are the stored result verbatim, which is what makes the
 * char slice identical to every other backend's and what keeps files written by older versions readable
 * with no format migration.
 *
 * That is TWO published objects, so the window is stated rather than hidden (falsification review): the
 * content link is the write-once election and the sidecar follows it, so a crash in between leaves an
 * entry content-complete and momentarily UNOWNED. Publishing the sidecar FIRST would close that window
 * and open a worse one — an ownerless writer's row could then be claimed by whoever raced it — so the
 * order stands and the entry stays unowned: fail-closed, refused by a host read face, never
 * mis-attributed, and never adopted by a later write (adoption is an adjudicated contract rule, not a
 * local choice — see the `put` body). What a `put` no longer does is walk over DAMAGE: a sidecar that
 * is present but unreadable stops the write loudly, where treating it as "no owner" would report
 * success and leave the entry unclaimable forever.
 */
export interface FileToolResultStoreOptions {
    /** Disclosure seat for an owner sidecar that is PRESENT but unreadable: {@link
     *  FileToolResultStore.ownerOf} answers `undefined` — the same word it uses for an entry nobody owns,
     *  so a host that authorizes on it reads damage as "unowned". Swallow-guarded. */
    onCorruptRead?: (info: CorruptReadInfo) => void;
}
export declare class FileToolResultStore implements ToolResultStore {
    private readonly dir;
    private readonly discloseCorrupt;
    constructor(root: string, opts?: FileToolResultStoreOptions);
    /** The one delivery point for {@link FileToolResultStoreOptions.onCorruptRead}. */
    private disclose;
    /** The directory a ref's files live in, and their shared stem. See {@link encodeRefFilename} for why
     *  a FOLDED name gets its own directory instead of sharing one with identity names. */
    private locate;
    private pathFor;
    /** #119 — the provenance sidecar's path. Same directory and stem as {@link pathFor}, so the pair is
     *  found by one encoding step and a ref that cannot be encoded has no sidecar either. */
    private ownerPathFor;
    put(ref: string, content: string, provenance?: ToolResultProvenance): void;
    /** #119 — the recorded owner, or `undefined` for an unknown ref AND for a stored-but-unowned one.
     *  Lenient by design on the READ face (see {@link readOwnerFile}). */
    ownerOf(ref: string): ToolResultProvenance | undefined;
    /**
     * #119 — read the owner sidecar. `loud` splits the two faces on purpose, the same split this backend
     * already applies to ref safety: a READ (`ownerOf`, an authorization question) degrades an
     * unreadable or malformed sidecar to "unowned" so a corrupt file cannot crash a page-back, while a
     * WRITE refuses to proceed over one it cannot read — silently treating damage as "no owner" is how a
     * write reports success and leaves the entry unclaimable forever (falsification review round 4).
     * A MISSING sidecar is not damage in either face: it is the honest "unowned".
     */
    private readOwnerFile;
    /** #119 — publish the owner sidecar (write-once, like the content file it accompanies). */
    private writeOwnerFile;
    get(ref: string, opts?: {
        offset?: number;
        limit?: number;
    }): ToolResultSlice | undefined;
    /**
     * Erase every entry owned by `sessionId` (interface: {@link ToolResultStore.deleteBySession}). Without
     * it a session deletion leaves this store's files on disk for the lifetime of the data root.
     *
     * Selection matches on the SIDECAR's `sessionId`, never on the filename. That is not a preference, it
     * is the only thing available: the mint separates segments with `~`, which is outside this backend's
     * filename charset, so every engine-minted ref folds to `<base>-<sha256 of the raw ref>` and the
     * session segment is gone from the name (and the readable base is a lossy, many-to-one prefix, so
     * matching on it would reach a neighbouring session's files). `taskId`, where an older write site
     * stamped one, is ignored — it narrows the same session.
     *
     * Both namespaces are swept: identity-named files in the store dir (refs a caller minted inside the
     * filename charset, and everything written before the injective mint) and {@link FOLDED_SUBDIR}.
     *
     * Removal order is CONTENT then SIDECAR, and it is load-bearing. Interrupted after the content unlink,
     * the next sweep still finds the sidecar, matches the same session and converges; interrupted after a
     * sidecar-first unlink, the content file would be left owner-less — readable, attributable to nobody,
     * and beyond the reach of every future session sweep. Same reasoning as `put`'s publication order, in
     * the opposite direction. The pair is then re-examined ONCE after both unlinks, which is what keeps
     * a `put` racing the two from leaving bytes whose owner this sweep removed — and what keeps that
     * re-examination from deleting a row a DIFFERENT session claimed in the same window (see the body).
     *
     * What the re-examination is NOT is atomic: it reads the owner and then acts, so a writer landing
     * inside THAT gap is narrowed, not excluded. Stated rather than locked away, because the locking that
     * would close it is ruled out one layer down — `fs-atomic.ts` §2.4 admits exactly one file lock, the
     * boot lock, on the premise that a data root has a single writing process (`FileStorageBackend`
     * acquires it before constructing this store; cross-process correctness is a database backend's job,
     * as everywhere else in this family). Under that premise there is no gap at all: both `put` and this
     * sweep are wholly synchronous, so nothing can interleave with either. The narrowing above is what
     * remains worth doing for a root someone shares anyway, and it costs one `readFileSync`.
     *
     * Failure posture: an entry that vanished between `readdir` and the unlink is honest absence (ENOENT
     * ok, the concurrent sweeper's row). Any OTHER unlink error throws — a deletion face that cannot
     * delete has to be loud — and because the sweep is idempotent, the retry after the operator clears
     * the cause resumes where it stopped. A sidecar that cannot be READ (EACCES, torn JSON, a shape that
     * is not the whole record) is counted `unattributable` and its content is left alone: a file we could
     * not read carries no evidence about whose it is, and this store's rows stay readable through `get`
     * even with a damaged owner record, so removing one on a guess would destroy another session's output.
     */
    deleteBySession(sessionId: string): ToolResultDeletionReport;
}
