import type { CorruptRecordRead, CorruptReplayTally } from "./corrupt-read-sink.js";
/**
 * Validate an engine-minted id (sessionId / tool-result ref) before using it as a path component. These are
 * `uuidv7` / an encoded tool-result ref — already filename-safe — but a strict guard is cheap
 * defense-in-depth: reject `.`/`..`/empty/separators so nothing can traverse out of the store dir.
 */
export declare function sanitizePathComponent(raw: string): string;
/**
 * Map a model-influenced memory `scope` to a safe directory name (CC `sanitizePath` parity). Replace every
 * non-alphanumeric run with `-`; if the result is over 200 chars, append a short content hash so two long
 * scopes can't collide. The ORIGINAL scope is the partition key the engine passes — this only affects the
 * on-disk dir name, never the stored data. Never `path.join` a raw scope.
 */
export declare function sanitizeScope(scope: string): string;
/** Resolve the data root (CC `getClaudeConfigHomeDir` analog): `$AGENT_DATA_DIR ?? ~/.ai-agent`,
 *  NFC-normalized, canonicalized once the dir exists. Creates the dir (0o700) if absent. */
export declare function resolveDataRoot(explicit?: string): string;
/** Ensure a directory exists with 0o700 perms (idempotent). */
/**
 * Atomically CREATE `target` carrying `content` — it appears in ONE step already fully written, no empty/partial
 * window. Fixes the create-then-write race: a reader/pruner can see a half-written
 * file, and a crash mid-write leaves a zero-byte target a retry would silently keep. Writes a temp in `target`'s
 * OWN dir (same FS → `link` is atomic) + fsync, then `linkSync`-publishes it; throws `EEXIST` if `target` exists.
 */
export declare function writeThenLink(target: string, content: string | Uint8Array): void;
export declare function ensureDir(dir: string): void;
/**
 * The 5-step atomic whole-file replace (§2.1). Skipping any of steps 3/5/6 is the classic corruption bug:
 *  1. open a temp under `root/tmp/` (same FS → rename is atomic),
 *  2. write the bytes,
 *  3. fsync the DATA (else ext4 "zero-length file after crash"),
 *  4. close → rename(tmp, target) (atomic intra-FS namespace swap),
 *  5. fsync the containing DIRECTORY (persists the rename itself).
 * On any failure the temp is unlinked so a crashed write leaves no scratch behind.
 */
export declare function atomicWriteFile(tmpDir: string, target: string, bytes: string): void;
/**
 * Read a JSONL file and return every COMPLETE, parseable record — the universal torn-tail recovery (§2.2):
 *  - the last segment, when no `\n` ends it, is judged as every line is (`@contract store.read_back_shape_judged`):
 *    an object is served, an unparseable remnant is a crash's partial write and is discarded,
 *  - any interior line that fails `JSON.parse` is skipped (never throws — `listRemoteAgentMetadata`
 *    parity), so a single corrupt interior line never poisons the whole replay.
 * A missing file → `[]`. This is why JSONL beats one big JSON blob: a torn tail can never corrupt the store.
 *
 * SHAPE (`@contract store.read_back_shape_judged`): parseability is not the judgement. Every store on this
 * format writes ONE JSON OBJECT per line, so a line parsing to a number, string, boolean, `null` or array
 * takes the road a torn line takes — disclosed and skipped — because it is the same fact: these bytes are
 * not what this store wrote. `judge` (optional) is the consuming store's own floor on an object ("does it
 * carry my members at all?"); a record it rejects is skipped as `missing-required` on the same road, so
 * the count below covers every skip.
 *
 * `onCorrupt` (optional): one `record` disclosure per skipped record, then — iff any was skipped — one
 * `replay` tally. The unparseable unterminated tail stays out of both by design: dying mid-write is the
 * crash shape this format exists to absorb. An unterminated tail that parses to a non-object is not that
 * shape (no prefix of an object parses to one), so it is disclosed like any other foreign line. The callback
 * is invoked as given — a seat that must not throw swallow-guards on ITS side.
 */
export declare function readJsonlRecords<T>(path: string, onCorrupt?: (info: CorruptRecordRead | CorruptReplayTally) => void, judge?: JsonlRecordJudge): T[];
/** A consuming store's floor on a parsed JSONL object: `accepts` = one of its records; `what` names a
 *  rejected one in the disclosure's words. */
export interface JsonlRecordJudge {
    accepts(record: object): boolean;
    what: string;
}
/**
 * An append-only JSONL log opened once with `O_APPEND` and held for the file's lifetime. One record =
 * one `JSON.stringify(...) + "\n"` written in a SINGLE `writeSync` call (never split across writes → the
 * only torn line possible is a crash tail, which {@link readJsonlRecords} drops). `fsync` is opt-in per
 * append (the checkpoint commit point ALWAYS fsyncs; session/memory cadence is the caller's `fsyncEvery`).
 */
/**
 * RB-144: THE canonical key for "one authority per physical location". Every file backend keeps a
 * module-level table so that N instances over one directory collapse into one CAS authority; getting the
 * KEY wrong reopens the defect the table exists to close, which this repo paid for four times (mailbox,
 * session store, then the agent/run/task-list stores still keying on a lexical `resolve()`). Point fixes
 * kept missing siblings, so the rule lives in ONE place they all call.
 *
 * Two normalizations, each for a demonstrated failure:
 *  - PHYSICAL PATH — a symlinked data dir (`/var` → `/private/var` on macOS, a bind-mount, a linked
 *    `~/.ai-agent`) otherwise yields two authorities for one directory, and both writers win the CAS.
 *  - CASE FOLD — on a case-insensitive filesystem `Foo/` and `foo/` are the same directory. The mint
 *    answers with the on-disk spelling, which folds the two only where the entry EXISTS; the fold here
 *    also covers a not-yet-created tail, and folding a PATH would name a different directory on a
 *    case-SENSITIVE volume (see the cc task-list store's lock target, which must not fold) — so it stays
 *    at this call site, which keys, rather than inside the mint, which answers about paths.
 *
 * The key is used ONLY for table lookup — never for I/O, so the on-disk name stays verbatim. A path that
 * does not exist yet resolves through its deepest existing ancestor; a path with no resolvable ancestor at
 * all keys on its lexical form (a store must not fail to construct because canonicalization is unavailable).
 */
export declare function canonicalStoreKey(p: string): string;
export declare class AppendLog {
    private fd;
    /**
     * RB-167 (2026-07-26): distinguishes "closed because the holder is done with it" from "closed for a
     * compaction swap". Only the first is permanent.
     *
     * Every store that compacts does close → rewrite → reopen, and the rewrite can fail (EACCES, ENOSPC, a
     * read-only mount, an immutable flag). The stores handled that in three different and individually
     * insufficient ways: three of them left the log CLOSED FOREVER, so one transient I/O error turned into
     * `log_closed` on every later write — and because these logs are now SHARED per directory, that bricks
     * every instance on it, not just the one that compacted. The fourth reopened in a `finally`, which only
     * covers the failure shapes where reopening still works and, worse, replaces the original error with the
     * reopen's own when it does not.
     *
     * The reopen therefore belongs here, lazily: a swap-closed log reopens on its next write, so a fault that
     * has cleared simply resolves itself, a fault that has not surfaces at the write (with its own error, the
     * original having already propagated from the compaction), and `close()` keeps meaning exactly what
     * RB-99 made it mean.
     */
    private disposed;
    private readonly path;
    /**
     * RB-438 — the file size as of the end of THIS log's last successful append (or of the last time it
     * observed the file at a record boundary). `-1` = unknown, re-establish on the next append.
     *
     * It is the cheap arbiter for "did the file change under us since we last left it whole": equal ⇒
     * nothing to check, different ⇒ read the last byte and repair if it is not a newline. See
     * {@link repairTail}.
     */
    private boundary;
    constructor(path: string);
    private sizeOrUnknown;
    /**
     * RB-438 (2026-07-31, hardening probe R2) — re-establish the last-complete-record boundary before a write.
     *
     * The constructor's {@link repairJsonlTail} (and the RB-167 lazy reopen) only ever ran at OPEN time, and
     * the RB-66 loop below only repairs a SHORT write — not a `writeSync` that THROWS mid-loop (ENOSPC/EDQUOT/
     * EIO after a partial `write(2)`, the classic full-volume shape) and not the `n <= 0` guard. Both leave
     * `written` bytes on disk with no terminating newline while the fd and this object stay live, and every
     * caller retries through the SAME AppendLog (`FileSessionStorage.appendEntry` throws before `recordEntry`,
     * so the in-memory leaf is unchanged and the CAS retry appends again). The retry's record was then GLUED
     * onto the remnant, so replay dropped BOTH — after that second append had returned and fsync'd. Measured:
     * records [1,2,3] + a torn remnant + appends 5,6 replayed as [1,2,3,6].
     *
     * Cost on the healthy path is ONE `fstat` per append (the sizes match and we return); the tail read only
     * happens when the file actually moved off a boundary, and it reads the last segment, not the file. A
     * legitimate concurrent appender (another process under the boot-lock handoff) leaves the file
     * newline-terminated, so it is adopted, not truncated; a whole record it left without its newline is kept
     * and ended, and only a partial one is cut.
     */
    private repairTail;
    /**
     * Append one record (`json + "\n"`). `fsync:true` makes the record durable before return.
     *
     * RB-66 (2026-07-25, red probe): `writeSync(fd, string)` issues ONE write(2) and may SHORT-WRITE — the
     * same defect class this file's own `writeThenLink` already documents and fixed ("silently SHORT-WRITES
     * "), never propagated to this shared primitive. A short write here leaves a torn
     * line with no trailing newline, so the NEXT append concatenates onto it and replay drops BOTH records.
     * Loop until every byte lands (write(2) returns how many it took) — the O_APPEND atomicity per call is
     * unchanged, and a genuine I/O error still throws.
     *
     * A write that fails leaves no bytes of its own: the file is cut back to where the write began, so a caller
     * that retries the record writes it once — a record that failed is never also half-present.
     */
    append(record: unknown, fsync: boolean): void;
    /**
     * Cut the file back to `startedAt` after a write that threw, provided the bytes past it are exactly this
     * write's own (`written` of them): a peer that appended after us owns what follows, and its bytes are not
     * ours to cut. Best effort — the error the caller must see is the write's, so a rollback that cannot run
     * answers false and the next append's tail repair judges what is left.
     */
    private rollBackFailedWrite;
    close(): void;
    /**
     * RB-167 — release the descriptor for a compaction SWAP. The next write reopens it lazily.
     *
     * Callers must use this rather than `close()` around a rewrite: `close()` means "this log is finished",
     * and a store that used it for a swap and then failed to rewrite left the log permanently unusable —
     * one transient I/O error becoming a permanent brick, shared by every instance on that directory.
     */
    closeForSwap(): void;
    private releaseFd;
}
/** Bytes read per positioned read while a file's tail is walked back to its last newline. */
export declare const JSONL_TAIL_READ_BLOCK: number;
/**
 * The longest last segment (in BYTES) a repair station will judge: V8's maximum string length in UTF-16 code units
 * (Node's `buffer.constants.MAX_STRING_LENGTH`). A segment of N bytes decodes to at most N code units, so one within
 * the bound can never make `toString` throw. One past it MAY still decode (multi-byte text has fewer units than
 * bytes) but is left unjudged either way: telling would mean reading and decoding up to three times the bound.
 */
export declare const JSONL_TAIL_SEGMENT_LIMIT: number;
/** A file's last segment (the bytes after its last `\n`, or the whole file when it holds none) — or the fact that it is
 *  too long to judge. `start` is the segment's offset; a file that ends on a newline has an empty segment at `size`. */
export type JsonlTailSegment = {
    unjudgeable: false;
    start: number;
    bytes: Buffer;
} | {
    unjudgeable: true;
};
/**
 * The last segment of an open file of `size` bytes, found by walking back from the end one block at a time to the
 * last newline: a read is bounded by the segment's length, never by the file's. The one reader of "what is the tail"
 * for every repair station; whether the segment is a record is {@link jsonlTextFormOf}'s to say.
 */
export declare function readTailSegment(fd: number, size: number, limit?: number): JsonlTailSegment;
/** Why a {@link BootLock}/{@link acquireStoreDirLock} acquisition was refused, as a branchable code. */
export type FileStoreLockErrorCode = 
/** A LIVE process (this one or another) already holds the directory. Steady state: it ends when that
 *  process ends. */
"store.dir_in_use"
/** Another process is mid-RECLAIM of a lock left by a crashed owner. Transient by construction — the
 *  claimant either finishes (and then owns the directory) or dies (and its gate is pruned in turn), so
 *  the honest advice here is "retry", NOT the "stop the other process" that `dir_in_use` earns. */
 | "store.dir_claiming"
/** The lock is in a state only a human should resolve — no parseable owner, or buried under nested
 *  reclaim gates from repeated crashes. Never pruned blindly; the message names what to remove. */
 | "store.lock_unreadable";
/**
 * The structured refusal of a file-store directory fence. Carried as a class (not a bare `Error`) so a
 * host can branch on {@link code} — "someone else owns this data dir" is an operator-actionable
 * condition (stop the other process / pick another root / move to a SQL backend), and telling it apart
 * from a genuine I/O failure by string-matching a message is exactly the fragility the codes exist to
 * remove. The messages are unchanged from the pre-code era, so existing text assertions still hold.
 */
export declare class FileStoreLockError extends Error {
    readonly code: FileStoreLockErrorCode;
    /** The lock file whose acquisition was refused. */
    readonly lockPath: string;
    /** The live owner's pid when the lock named one (absent for an unreadable lock or a lost race). */
    readonly ownerPid?: number | undefined;
    readonly name = "FileStoreLockError";
    constructor(code: FileStoreLockErrorCode, message: string, 
    /** The lock file whose acquisition was refused. */
    lockPath: string, 
    /** The live owner's pid when the lock named one (absent for an unreadable lock or a lost race). */
    ownerPid?: number | undefined);
}
/**
 * The coarse boot guard (§2.4): an `O_EXCL` owner file (`root/LOCK` for the whole data dir; one per store
 * ledger directory via {@link acquireStoreDirLock}) that forbids two processes sharing it. A second
 * instance fails fast ("another instance owns this data dir"). A STALE lock (the process that WROTE it is
 * gone — see {@link isProcessLive}, which is not the same question as "is that pid free") is pruned and
 * re-acquired — `proper-lockfile`/CC `concurrentSessions` parity.
 *
 * This is the family's ONE lock MECHANISM (the per-operation CAS stays in-process: one event loop + a
 * per-token async mutex, so there is NO per-operation flock). A holder of one of these fences guarantees
 * the in-process model's premise — a single writer to the directory — actually holds. Cross-process
 * CORRECT concurrency (many writers at once) is still the Pg/TiDB backend's job, by design: this fence
 * refuses the second writer, it does not coordinate it.
 */
export declare class BootLock {
    private readonly lockPath;
    private held;
    /** The verdict of the ONE release this holding gets, replayed to any later caller (see {@link release}). */
    private spentVerdict;
    constructor(lockPath: string);
    /** Acquire the lock or throw. Reclaims a lock whose recorded OWNER is gone (see below). */
    acquire(): void;
    /**
     * @param depth how many stale reclaim gates deep we already are (see the gate below). Bounded so a
     *  pathological chain demands an operator instead of recursing without end.
     */
    private acquireWithin;
    /** Publish our lock, or say out loud that a fresh contender got there first. */
    private publishOrLoseRace;
    /** Take back a lock THIS process published and could not remove on release (never someone else's). */
    private pruneOwnAbandoned;
    private writeLock;
    /**
     * Key for the {@link abandonedLocks} registry: the RESOLVED path, deliberately NOT case-folded. Folding
     * (as the store-table key does) would alias `/data/foo/LOCK` and `/data/Foo/LOCK` — two genuinely
     * different files on a case-sensitive filesystem — and this registry authorizes a DELETE. Missing an
     * entry costs a loud refusal; a false hit costs someone else's lock.
     */
    private get abandonKey();
    /**
     * Read the lock as one of three ANSWERS, never as one "undefined" that means all of them: absent (nobody
     * holds it), a named owner, or unreadable. Collapsing the last two is what let a release that could not
     * even READ the file report the lock as gone — after which the bookkeeping was dropped while the file
     * still named this process, and the directory was refused for the rest of the process's life.
     */
    private inspectLock;
    /**
     * Release the lock (only if we hold it), ONCE. Best-effort; never throws.
     *
     * @returns whether the lock is now GONE — removed, or provably somebody else's. A `false` means the file
     * is still there and this process cannot prove it is not the owner of record, which the caller must not
     * treat as released: every later acquisition would read a live owner (us) and refuse a directory nobody is
     * using. The path is remembered as ABANDONED so a later acquisition in this process can take it back
     * rather than refuse a directory nobody is using — a lock carrying our own pid can only be ours
     * ({@link pruneOwnAbandoned} removes and republishes it; {@link resumeAbandoned} re-arms a holder over
     * the file as it stands).
     *
     * ONE-SHOT, and that is load-bearing. This object stops being a holder the moment it is ASKED to release
     * — before the filesystem is touched — and a second call REPEATS the first verdict instead of acting on
     * it again. A lock file names its publishing PROCESS, so two BootLocks over one path in this process are
     * byte-identical on disk: after a FAILED release the path can legitimately be re-published (a later acquisition here
     * finishing our cleanup), and a stale object that "removed its own lock" a second time would be deleting
     * the SUCCESSOR's fence while both sides believed they held the directory — the double-writer this whole
     * mechanism exists to refuse, minted from bookkeeping instead of from a race. So a second release answers
     * the first question again and touches nothing.
     */
    release(): boolean;
    /**
     * The filesystem half of {@link release}: remove the lock while the file still names US, and report what
     * the directory looks like AFTERWARDS (never what we intended).
     *
     * "Still names us" is a READ, and `unlink` cannot be made conditional on content — so, exactly as in
     * {@link resumeAbandoned}, an external removal landing between the two lets this delete a file that is no
     * longer the one we read. That is the advisory-pid-file mechanism's exposure, identical for every holder
     * and every moment of a holding, not something this ordering introduces; the re-inspection below is why
     * the ANSWER stays honest even then.
     */
    private removeOwnLock;
    /**
     * Resume holding a lock THIS process published and could not remove on release — the state a zero-ref
     * entry in {@link acquireStoreDirLock}'s table is in, and the only way a joiner may ride that entry.
     *
     * Pure INSPECTION of the disk: it publishes nothing and removes nothing. The claim is honoured only
     * when the file still there is the one we left (our own pid) AND no publish on this path has spent the
     * abandoned note since — any successful publish (ours, or a reclaim) makes some OTHER object the holder,
     * and re-arming here would put two holders on one path, exactly what one-shot {@link release} closes. A
     * `false` says the bookkeeping can no longer be trusted and the caller must go back to the disk: the
     * file may have been cleared (an operator, a tmp cleaner) and the directory since taken by another
     * OS process.
     *
     * A successful resume SPENDS the note, for the same reason a successful publish does: the note is what
     * authorizes {@link pruneOwnAbandoned} to DELETE this file, and while somebody holds the path that
     * authorization must not exist — a second acquisition here is a genuine conflict that has to be refused
     * by name, not a leftover to be cleaned up over a live holder. A later failed release writes it again.
     *
     * What a `true` establishes, exactly: the file was the one we left AT THE MOMENT WE LOOKED. It is not an
     * atomic handoff and cannot be — this fence is an advisory pid FILE, so nothing stops an operator (or a
     * tmp cleaner) from removing it the instant after any check, here or hours into an ordinary holding, and
     * a foreign process publishing over the gap. That exposure belongs to the mechanism and is the same for
     * a fence nobody ever released; what this check removes is the far wider window in which the removal
     * already happened and the table would have kept vouching for it anyway. The branch where the file is
     * ALREADY gone is not re-armed here at all: it falls through to a real acquisition, whose `O_EXCL`
     * publish is the atomic test-and-set, and a foreign owner there is refused by name.
     */
    resumeAbandoned(): boolean;
}
/** One holder's share of a directory fence. Releasing is idempotent and only the LAST share unlinks. */
export interface StoreDirLockLease {
    /** The lock file this share is counted against (inspection/tests). */
    readonly lockPath: string;
    /** Drop this share. The lock file survives until every share over the directory is released. */
    release(): void;
}
/**
 * Take (or JOIN) the cross-process writer fence for one store directory.
 *
 * The file family's premise everywhere is "one writer per data directory"; before this, the ONLY thing
 * enforcing it was the `root/LOCK` a `FileStorageBackend` takes — which a store constructed directly
 * (every one of them is a root export) never sees, and which never covered a store the backend does not
 * construct at all. A second OS process then replayed a PRIVATE view of the same ledger: both sides win
 * a once-only CAS, and the one that never loaded the other's rows ERASES them at its next compaction.
 * So the fence belongs to the directory that holds the authority, taken by whoever opens it.
 *
 * Semantics: exclusive across PROCESSES (a live foreign owner ⇒ {@link FileStoreLockError} naming the
 * pid; a dead owner's lock is pruned as stale), refcount-JOINED inside this process (siblings over one
 * directory share the fence, and the last {@link StoreDirLockLease.release} unlinks it). `dir` must
 * already exist (the caller's `ensureDir`), since the fence lives inside it.
 */
export declare function acquireStoreDirLock(dir: string, opts: {
    label: string;
    fileName?: string;
}): StoreDirLockLease;
/**
 * design/84 Seam B (TOC profile) — a per-scope CONSOLIDATION lock factory for the file backend's
 * {@link import("../../core/consolidate-scope.js").ConsolidateScopeDeps.acquire} injection point. A single
 * machine may run a periodic-consolidation timer AND a task-end inline pass concurrently; this advisory lock
 * stops two processes consolidating the SAME scope at once (the in-process model the file backend assumes —
 * see {@link BootLock} — does not cover a second OS process with its own timer).
 *
 * `acquire(scope)` takes a {@link BootLock} over `lockDir/<sanitizedScope>.consolidate.lock`. It returns a
 * `release` callback on success, or `undefined` for EVERY refusal that lock knows how to make — a live
 * owner (including this process: a second pass here is still a conflict), a takeover in flight, an
 * unreadable file — because the caller's whole vocabulary is "busy → skip this pass", a no-op that costs
 * one deferred consolidation. A stale lock is reclaimed by the same protocol as any other, which is the
 * point of not hand-rolling one: this factory used to prune with a bare `unlink` and re-publish, and two
 * passes that had both read the same dead owner would then both prune and both publish — one deleting the
 * other's fresh lock, leaving two processes consolidating the SAME scope, which is exactly what the lock
 * exists to prevent. The reclaim gate serialises that, the depth cap bounds it, and the identity re-read
 * makes sure a reclaim prunes the owner it came for. One lock MECHANISM in this family, not two.
 *
 * The implementation lives HERE (the deployment shell's persist/exec axis), NOT core — core only DEFINES
 * the injection point (the constitutional split: gate-presence in core, mechanism in the profile).
 */
export declare function createFileConsolidationLock(lockDir: string): (scope: string) => (() => void) | undefined;
/**
 * A process identity that survives PID REUSE: a pid, WHEN that pid's process started, and the host the
 * observation was made on.
 *
 * A pid alone is not an identity. The OS hands the same number out again once its holder exits, so a lock
 * (or lease, or any other durable "who owns this" record) carrying only a pid says no more than "somebody
 * with that number" — and after a crash, an unrelated process that inherits the number makes the dead
 * owner's record read as a LIVE owner for as long as that process lives. Adding the START TIME makes the
 * record falsifiable: the pid narrows the candidate to one process, the start time decides whether it is
 * still the SAME one.
 *
 * The start time is carried TWICE, against two different clocks, and that is the whole robustness of the
 * thing. Both numbers come from ONE reading of the process's elapsed time, but one is subtracted from the
 * WALL clock and the other from the host's UPTIME — and a platform that leaves one of them unmoved when
 * the wall clock is corrected moves the other. (Where `ps` measures elapsed time against the boot clock, a
 * corrected wall clock shifts the derived wall-clock start and leaves the uptime-relative one alone; where
 * it measures against a stored absolute start, it is the other way round.) A comparison that insists on
 * BOTH before calling a record stale therefore cannot be talked into a reclaim by a clock correction —
 * which matters because that reclaim would be of a LIVE writer's lock. See {@link isProcessLive}.
 *
 * Both are `null` when this host cannot produce a start time (no `ps`, a cut-down `ps` that rejects the
 * query, a probe too slow for its own reading to be worth anything). That is a plain "unknown" — never a
 * zero and never a guess — and every consumer treats an unobtainable fingerprint as UNFALSIFIABLE, keeping
 * the pid-only verdict: the degrade lands on yesterday's behaviour rather than on a wrong reclaim, and
 * says so out loud once.
 */
export interface ProcessFingerprint {
    /** The process id the rest of the record is about. */
    readonly pid: number;
    /** Start of that pid's process against the WALL clock, to the second; `null` when unobtainable here. */
    readonly startedAtMs: number | null;
    /** The same start against this host's UPTIME (ms after boot), to the second; `null` likewise. */
    readonly startedSinceBootMs: number | null;
    /** The host both were read on — a fingerprint from ANOTHER host is comparable to nothing here. */
    readonly host: string;
}
/**
 * The fingerprint of a process: this one when called with no pid, any other pid when named.
 *
 * ONE derivation for both halves of the mechanism — the record a holder WRITES and the probe a later reader
 * verifies it with — on purpose. Deriving our own start time from `process.uptime()` instead would be
 * cheaper, but it measures from Node's own initialisation rather than from the kernel's `exec`, so a busy
 * machine can put the two derivations seconds apart. A reader comparing across them would then read a
 * healthy holder as stale and RECLAIM A LIVE LOCK — a strictly worse failure than the pid reuse this fixes.
 * One derivation makes the tolerance a rounding allowance instead of a guess about bootstrap latency.
 *
 * The self case is memoized: our start time cannot change, the probe is a subprocess, and a lock publish is
 * the only place that needs it — so a process pays for it at most once, whatever it opens.
 */
export declare function processFingerprint(pid?: number): ProcessFingerprint;
/**
 * Is the process a record names still the one that wrote it? ONE predicate, two terms: the pid is running,
 * AND the fingerprint has not been falsified.
 *
 * "Not falsified" is the load-bearing wording, and it is what every degrade lands on. A record with no
 * `startedAtMs` (the pre-fingerprint spelling, or a host that cannot produce one), a record written on
 * ANOTHER host (its start time was measured against a different boot — comparing it here would be
 * arithmetic on unrelated numbers), and a probe that cannot answer all reach the same verdict: LIVE. The
 * asymmetry is deliberate and is the whole safety argument — a false "dead" reclaims a directory out from
 * under a working process (two writers, and the ledger loses whichever rows the loser never loaded), while
 * a false "live" costs a refusal an operator can act on.
 */
export declare function isProcessLive(record: {
    readonly pid: number;
    readonly startedAtMs?: number | null;
    readonly startedSinceBootMs?: number | null;
    readonly host?: string | null;
}): boolean;
