import { type WorkflowJournalEntry, type WorkflowJournalStore, type ResumeClaimArgs } from "../../core/workflow-journal-store.js";
import { type CorruptReadInfo } from "./corrupt-read-sink.js";
/**
 * SVC-2 — the shared size bound for a journaled workflow-agent entry, so ALL backends answer an oversize
 * TaskResult with the SAME refusal (no dialect divergence). Inlined from service's `workflow-journal-limits` on
 * onboarding — the 5 MiB figure aligns with TiDB's ~6 MiB per-row txn-entry-size-limit (a row over it is
 * REJECTED with "entry too large"). PostgreSQL TEXT has no such low limit, so without a shared bound the same
 * large entry would journal on PG and fail on TiDB; the shared bound makes every backend refuse at the same
 * byte and with the same code (`workflow.journal_oversize`) before the row reaches the medium. Measured in
 * BYTES (UTF-8) — a multibyte result can be larger than its `.length` in chars.
 */
export { MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "../../core/workflow-journal-store.js";
/**
 * RB-242 — how long a resume claim survives WITHOUT an engine release. This is the backstop for an engine that
 * died holding one (crash / kill -9 / box reboot): it never reaches its `finally`, so nothing but expiry can
 * ever free the row, and a source run whose resume is wedged forever is a worse failure than the fork this
 * guards. One hour is the balance: comfortably longer than the gap between a resume's admission and its first
 * journal write (the window in which a fork actually duplicates work), and short enough that a crashed box does
 * not lock a run out for a working day. A LIVE engine's own release is the normal path and is prompt — this
 * value is only ever reached by a run that is no longer running.
 */
export declare const RESUME_CLAIM_TTL_MS: number;
/**
 * RB-356② — a release REFRAINS from unlinking its own claim once the claim is this close to (or past) its
 * expiry. Rationale: a peer may legitimately take a claim over the instant it expires, and `release` is a
 * read-then-unlink pair with no compare-and-delete syscall between them — so the one situation where the
 * unlink could destroy a SUCCESSOR's claim is precisely the one where the record we just read is already
 * takeable. Refusing to unlink there is free: an expired claim blocks nobody (the next acquirer takes it over)
 * and the sweep below reaps it. The margin only has to cover the scheduling gap between this method's read and
 * its unlink; seconds is orders of magnitude more than that, and losing the last five seconds of a one-hour
 * lease costs nothing.
 */
export declare const RESUME_CLAIM_RELEASE_GRACE_MS = 5000;
/**
 * RB-356③ — how often (per store instance) an acquisition also SWEEPS the claims directory. The header's
 * "the TTL reaps any orphan" was an aspiration, not a mechanism: nothing ever reaped anything. An expired
 * claim was only removed if a LATER resume off the SAME `(sourceRunId, scope)` came along to take it over —
 * and the normal shape is exactly one resume per source run, so a crash (or any release that could not
 * complete) leaked one small file per source run for the lifetime of the data root. The sweep makes the
 * comment true. Throttled because it is a `readdir` on the claims directory: it is opportunistic hygiene, not
 * a correctness path (takeover, not the sweep, is what unblocks a contended claim promptly).
 */
export declare const RESUME_CLAIM_SWEEP_INTERVAL_MS = 60000;
/**
 * RB-356③ — the sweep only reaps a claim that has been expired for at least this long. A just-expired claim
 * is the exclusive business of whoever is taking it over right now; the sweep has no deadline to meet, so it
 * stays well clear of that handover rather than adding a second unlinker to it.
 */
export declare const RESUME_CLAIM_REAP_GRACE_MS: number;
export interface FileWorkflowJournalStoreOptions {
    /** Disclosure seat for a ledger line this store DROPPED on replay (not JSON, not a record, or a record
     *  with none of this ledger's members) — a resume that silently finds "nothing to replay" re-runs the
     *  work with no signal. Never fires on a missing ledger; swallow-guarded. */
    onCorruptRead?: (info: CorruptReadInfo) => void;
}
export declare class FileWorkflowJournalStore implements WorkflowJournalStore {
    private readonly fsyncEnabled;
    private readonly dir;
    /** RB-242: resume claims live in their own subdirectory so a claim file can never be mistaken for a run
     *  ledger by anything that enumerates `workflow-journal/` (GC, the torn-tail replay, an operator's `ls`). */
    private readonly claimsDir;
    private readonly shared;
    private readonly sharedKey;
    private closed;
    /** RB-356③: last {@link sweepExpiredClaims} timestamp — throttles the reap to one `readdir` per interval.
     *  Per INSTANCE, not per directory: the sweep is idempotent hygiene, so a second instance doing its own
     *  first sweep is harmless (unlike the fd/scope caches above, which are authority over the directory). */
    private lastClaimSweepAt;
    /** RB-61: one AppendLog per run (torn-tail truncation at open) — replaces the raw fd map. */
    private get logs();
    /** Per-run committed scope, cached at first touch — the append-path scope guard must NOT re-read the whole
     *  ledger per append (a journaled result can be MBs; a many-agent workflow would go O(n²) on disk). One replay
     *  discovers it; every later append compares against the cache. */
    private get scopes();
    /** The swallow-guarded corrupt-read seat (undefined = the old silent tolerance). */
    private readonly discloseCorrupt;
    constructor(root: string, fsyncEnabled?: boolean, opts?: FileWorkflowJournalStoreOptions);
    private pathFor;
    /** CC local form: the run's on-disk journal path for the notification diagnostics line.
     *  Traversal-shaped ids (LLM-controlled on some paths) degrade to undefined, never throw. */
    locator(runId: string): string | undefined;
    /** Replay a run's ledger: last-writer-wins per ordinal; a torn/unparsable line is skipped (uncommitted). */
    private replay;
    append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
    load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
    /**
     * RB-242 — the claim file for `(sourceRunId, scope)`. Keyed by BOTH: a cross-scope caller already
     * resolves to an empty journal (it cannot fork anything), so letting it share one claim key would only hand
     * any tenant a denial-of-resume lever over another's runs. The scope is folded in as a digest rather than
     * a sanitized literal so two distinct scopes can never collapse onto one filename by sanitization.
     * Throws (via `sanitizePathComponent`) on a traversal-shaped runId — callers degrade, see below.
     */
    private claimPathFor;
    /**
     * Read a claim file, KEEPING the exact bytes it was parsed from. A missing / truncated / malformed record
     * reads as NO claim (takeable).
     *
     * RB-356② — the raw bytes are the record's identity token. POSIX has no compare-and-delete, so the only way
     * this store can check "the file I decided about is still the file on disk" immediately before removing one
     * is to re-read and compare (see {@link unlinkIfUnchanged}). Since RB-354 a claim is published whole, so
     * "unparsable" no longer includes "a peer is mid-acquire" — it means external truncation/corruption, which
     * genuinely is not a live claim.
     */
    private readClaimRaw;
    /**
     * RB-356② — remove a claim ONLY if the bytes on disk are still the ones the decision to remove it was made
     * on. This narrows the decide→delete window to two adjacent syscalls with no user-space work between them,
     * and everything a peer can publish into that window is published atomically (RB-354), so the re-read sees
     * either the bytes we judged (safe to unlink) or a peer's complete new claim (we back off and re-contend).
     * `expectedRaw === undefined` = the file did not parse: there is no live claim to destroy.
     */
    private unlinkIfUnchanged;
    /**
     * RB-356③ — reap claims nobody can be holding any more (see {@link RESUME_CLAIM_SWEEP_INTERVAL_MS}).
     * Best-effort throughout: a sweep failure must never turn an admission into an error. Also drops the
     * `.tmp` scratch a crash could strand mid-publish, on the same TTL horizon.
     */
    private sweepExpiredClaims;
    /**
     * RB-242 — admission for a resume off `sourceRunId`. The LOCAL (TOC) twin of the SQL-backed
     * `idem_key UNIQUE` + TTL: publishing the claim file is one atomic namespace operation that fails EEXIST
     * against anyone who got there first, so two processes racing on the same box (the shape the in-process
     * `startWorkflow` guards cannot see — a shell restart resuming a run a still-live process is already
     * resuming) produce exactly one winner.
     */
    resumeClaim(input: ResumeClaimArgs): Promise<{
        granted: boolean;
        holder?: string;
    }>;
    /** RB-242 — release ONLY a claim this `newRunId` still holds. A claim that is already gone (TTL-reaped
     *  and possibly taken over) is a no-op: unlinking a successor's claim would re-open the very fork this prevents. */
    releaseResumeClaim(input: ResumeClaimArgs): Promise<void>;
    /** GC (service extra, mirrors the SQL twins): drop the run's ledger once the run is terminal + retained. */
    deleteByRun(runId: string): Promise<number>;
    /** Release ledger fds (graceful shutdown; parity with the other file stores' dispose).
     *  Refcounted: the fds are shared with any other instance on the same directory, so the LAST holder
     *  closes them. Idempotent — a second dispose() on the same instance must not decrement twice and pull
     *  the ledger out from under a live peer. */
    dispose(): void;
}
