import { type PutRulesOptions, type SessionPolicyStoreOptions, type SessionPermissionRules, type SessionPolicyStore, type SessionRulesRecord, type StoredSessionRules } from "../../core/session-policy-store.js";
/**
 * design/99 §E6 — file-backed {@link SessionPolicyStore} for the local (TOC) backend: one JSON row file per
 * `(sessionId, principal)`, plus — once that key has ever been stamped — a second, tiny file beside it holding
 * the number the erase contract on {@link SessionPolicyStore.deleteBySession} says must outlive the row. Both
 * names share a stem, and the mark's suffix is what every directory sweep here tests first
 * ({@link MARK_SUFFIX}). Semantics otherwise **byte-for-byte identical to `InMemorySessionPolicyStore`** (the
 * cross-backend equivalence contract). CAS-rev OCC + the shape, entry and tighten-only judgments are the ONE write judge
 * (`judgeSessionRulesWrite`) and its row reads the ONE shape reading (`readSessionRulesShape`) — no re-implemented rule logic
 * that could drift.
 *
 * Atomicity: the file backend is single-process (the `FileStorageBackend` boot lock guarantees ONE writer per
 * data dir), and `getRules`/`putRules` read-check-write SYNCHRONOUSLY (no await between the rev read and the
 * atomic write), so the read-modify-write is atomic in the one event loop — exactly the InMemory store's premise.
 * Cross-process CORRECT concurrency is the Pg/TiDB backend's job (a CAS WHERE clause), by design.
 */
/** Coordinates of one damaged policy file the store observed (the {@link FileSessionPolicyStoreOptions.onCorruptRead}
 *  payload): the keyed read REFUSES it, the enumeration skips it. Named rather than inline so the backend option
 *  that forwards it names the SAME shape. */
export interface SessionPolicyCorruptReadInfo {
    /** Always a whole-file disclosure (the {@link import("./corrupt-read-sink.js").CorruptReadInfo} level). */
    kind: "file";
    /** The session whose read observed the corrupt row. On the enumeration face this is the session being
     *  ENUMERATED: the row it refuses is that session's ({@link attributeSessionPolicyRow}), the row it omits is one
     *  nothing on disk attributes to anybody. */
    sessionId: string;
    /** Present only when the read was keyed by a principal (the enumeration face has none). */
    principal?: string;
    /** Absolute path of the offending file — a rules row, or a key's high-water mark (`*.hwm.json`). The refusals'
     *  messages never carry it: this is where a location is reported. */
    path: string;
    /** What was observed — the same words the refusal's message carries after its grade (unparseable / malformed / unreadable);
     *  for a high-water mark, `high-water mark ` + the reader's fault predicate (is unparseable / is not a JSON object / …). */
    reason: string;
}
export interface FileSessionPolicyStoreOptions extends SessionPolicyStoreOptions {
    /** Disclosure sink for a damaged rules file or high-water mark (see {@link FileSessionPolicyStore.refuseDamaged} and
     *  its mark sibling): it fires WITH the refusal on the keyed read / write and on the enumeration of the session that
     *  owns the row, and on its own where the enumeration omits a row nothing attributes to anybody or the erase sweep
     *  leaves a damaged mark. Never fires on plain ENOENT.
     *  Swallow-guarded: a throwing sink cannot change the refusal. */
    onCorruptRead?: (info: SessionPolicyCorruptReadInfo) => void;
}
/**
 * The suffix that tells a MARK file apart from a rules ROW in the shared directory — injective by
 * construction, not by convention: `sanitizeScope` ends every row's basename with a 64-hex digest, so the
 * character before a row's `.json` is always a hex digit and no row can be spelled `*.hwm.json`. Every sweep
 * of this directory tests it FIRST: a mark must never be listed as a row, nor judged as crash litter.
 */
export declare const SESSION_POLICY_MARK_SUFFIX = ".hwm.json";
/** What one mark file carries: the number, plus the row's attribution fields (the filename is a one-way hash,
 *  so those are the only way a sweep can tell whose mark it is). Attribution is OPTIONAL on the read side —
 *  this store always writes it, a hand-written file need not, and a mark with no owner is simply nobody's. */
export interface StoredHighWaterMark {
    __hwm: number;
    /** The lineage the key's NEXT row starts (bumped by every erase); absent = 0 (a mark written before lineages). */
    __gen?: number;
    __sid?: string;
    __principal?: string | null;
    /** The erase's tombstone (a torn row purged with no mark): writes are refused until an operator deletes this file. */
    __lineage?: "lost";
}
/** The ONE reader's answer: the mark (`null` = no file), or the fault that makes the bytes not a mark. `reason` is a
 *  predicate ("is unparseable"), so every wrapper — the keyed store's refusal, the adoption leg's, the disclosure —
 *  says the same fault in the same words. */
export type SessionPolicyMarkRead = {
    mark: StoredHighWaterMark | null;
} | {
    corrupt: {
        reason: string;
    };
};
/**
 * The ONE reader of a mark file — the number it holds (`{ mark }`, `null` for no file: the ordinary state of a key
 * nobody erased), or the fault that makes the bytes not a mark (`{ corrupt }`). Every outcome but those two is a
 * refusal at the caller: for a ROW "unreadable" degrades to "absent" and costs nothing, while for a mark absence
 * reads as ZERO, the rollback the erase contract forbids. It only JUDGES, carrying no sentence and no location,
 * because the wording depends on the audience: a store method answers an end user (session, fault, repair; the
 * location goes to the disclosure sink), the adoption leg answers an operator (it names the file). An IO failure
 * propagates raw. Exported because adoption moves these files too; a second, more tolerant reader there is how the
 * guarantee goes quietly missing.
 */
export declare function readSessionPolicyMark(path: string): SessionPolicyMarkRead;
/** Why one row file's bytes are not a row. `unparseable` is the ONE grade saying they are not JSON at all — the
 *  only evidence the erase sweep's crash-artifact ruling rests on. `reason` is the disclosure text, so two faces
 *  judging the SAME file name the same fault in the same words. */
export interface SessionPolicyRowFault {
    grade: "unparseable" | "malformed";
    reason: string;
}
/** One row file's bytes → the JSON object every face of this directory requires before reading a field off it.
 *  Split from the rules-shape judgment below because the erase sweep and the ownership rebind treat a row as
 *  bytes-with-attribution: a malformed-rules row is still erasable and still travels with its key. */
export declare function judgeSessionPolicyRowObject(raw: string): {
    row: Record<string, unknown>;
} | {
    corrupt: SessionPolicyRowFault;
};
/**
 * The SHAPE of a stored row, judged ONCE for every face that serves its rules to a reader — the per-key read
 * and the session-wide enumeration alike, so one store cannot answer two things about one file.
 *
 * Required: `rev` >= 1 (a rev-0 row would read as the live reader's SYNTHETIC empty seed); `gen`, when present, a
 * whole number (a STRING `rev` poisons the CAS arithmetic); the rules themselves read by the ONE shape reading
 * (`readSessionRulesShape` — every key a bucket, every bucket a list of strings, a `grammar` this engine reads), so a row it
 * cannot read as rules (a key that is no bucket, a later grammar) is refused like any malformed row, never read as fewer rules.
 * The `__sid`/`__principal` metadata is STRIPPED so the value is byte-identical to the in-memory twin's.
 */
export declare function judgeSessionPolicyRowShape(row: Record<string, unknown>): {
    rules: StoredSessionRules;
} | {
    corrupt: SessionPolicyRowFault;
};
/** Whose key one row file is, as the bytes on disk show it. */
export interface SessionPolicyRowOwner {
    sessionId: string;
    /** The principal the SAME evidence names: a string, `null` for the anonymous lane, `undefined` when it names none it can spell. */
    principal: string | null | undefined;
}
/**
 * The ONE answer to "whose row is this file?" for every face that sweeps the directory — the enumeration, the erase
 * and the ownership rebind — so a damaged row is one session's fact on all three. The row's own `__sid` when its
 * bytes are a JSON object carrying one; otherwise the `__sid` of its same-stem high-water mark, which every stamp
 * writes BEFORE the row, so a torn, non-object or unreadable row still has it. `undefined` = nothing on disk names
 * an owner (no mark, or one that is not a mark or carries no session): such a row can be judged by nobody's key, and
 * each face keeps its own answer for it. A mark that cannot be READ is no evidence either way, so it THROWS (raw,
 * from the reader) and the caller refuses rather than guess: guessing "nobody" would let an erase sweep another
 * session's restriction, or a rebind strand it, on one transient read fault. When the answer rested on the mark,
 * the read is handed back (`markRead`) so a caller that then acts on the mark acts on the SAME observation.
 */
export declare function attributeSessionPolicyRow(dir: string, file: string, row: Record<string, unknown> | undefined): {
    owner: SessionPolicyRowOwner | undefined;
    markRead?: SessionPolicyMarkRead;
};
export declare class FileSessionPolicyStore implements SessionPolicyStore {
    private readonly dir;
    private readonly onCorruptRead;
    /** {@link SessionPolicyStoreOptions.mountedToolNames}, read once at construction. */
    private readonly mountedToolNames;
    /** #132 — the ADOPTED identity of a local-owner root's anonymous lane, or undefined on an
     *  unadopted (or principal-adopted) root. A local-owner adoption rebinds the anonymous
     *  `[sid,null]` estate rows to `toPrincipal`; until the deployment's principal wiring lands
     *  (`REQUIRE_PRINCIPAL` et al. — recorded `migrated:false` in the config account, an operator
     *  action), the runtime still queries anonymously, and ENOENT there would silently drop the very
     *  tighten-only deny rules the rebind moved. Adoption is a transfer of the whole anonymous
     *  identity, so on such a root the anonymous key IS the adopted principal — reads and writes
     *  alike (a window-era anonymous write must not mint a fresh orphan row). */
    private readonly anonymousAlias;
    constructor(root: string, opts?: FileSessionPolicyStoreOptions);
    /** The one delivery point for {@link onCorruptRead}. */
    private disclose;
    /** The ONE refusal for a rules file that is PRESENT but cannot be served as rules — every damage grade lands
     *  here (`unreadable` bytes, `unparseable` JSON, a `malformed` row), so the keyed read and the write that reads
     *  before it answer with one voice. Session rules only TIGHTEN: a row is the restriction an operator put on the
     *  session, so reading a damaged one as "no rules" would drop the restriction itself — the wrong direction for a
     *  store whose every other face refuses a loosening. ABSENCE (ENOENT) is the only "no rules"; it never reaches
     *  here. The disclosure sink fires with the refusal (swallow-guarded: a throwing sink cannot turn it back into an
     *  answer) and is the ONLY carrier of the file's path: the thrown message reaches end users through a host's error
     *  body, so it names no on-disk location. Two ways out, both the operator's: fix the file, or delete it — deleting is an explicit "no rules" and
     *  gives up the rules the file held. */
    private refuseDamaged;
    /** The sibling of {@link refuseDamaged} for the key's high-water MARK, and its only other voice: one disclosure
     *  shape, one code, a message naming the session, the fault and the repair but never a location (the path rides
     *  the sink, as the row's does). It refuses rather than read the mark as absent: that reads as ZERO and restarts
     *  the key's rev (see {@link readSessionPolicyMark}). Deleting the file gives up the number it held. */
    private refuseDamagedMark;
    /** The sweeps' (enumeration / erase) refusal when a file they must READ cannot be — the directory, or a mark that
     *  decides whose a row is or holds the number an erase must keep. Same voice as the two above: session, fault,
     *  repair, and the location only on the disclosure. Nothing is guessed and nothing is removed; a retry re-reads. */
    private refuseUnreadable;
    /** {@link attributeSessionPolicyRow} for a sweep: an unreadable mark becomes {@link refuseUnreadable}. */
    private ownerOf;
    /** A mark the erase must act on, read in the sweep's voice ({@link refuseUnreadable} when it cannot be read). */
    private sweepMark;
    /** The directory a sweep enumerates; absent = empty, unreadable = {@link refuseUnreadable}. */
    private sweepEntries;
    /** The keyed read of the mark for a write: the number, `null` for a key nobody stamped; a file that is there is a
     *  mark or a refusal ({@link refuseDamagedMark}), never "no mark". One that cannot be READ refuses in the same
     *  voice — the raw errno error names the file (EACCES does). */
    private readMark;
    /** `(sessionId, principal)` → a safe, INJECTIVE filename (sanitizeScope appends the full sha256 of the raw
     *  composite key, so distinct keys never collide on disk). On a local-owner-adopted root the
     *  anonymous lane resolves to the adopted principal's key (#132 — see {@link anonymousAlias}). */
    private pathFor;
    /** The shared, injective stem of a key's two files — the row (`<stem>.json`) and its mark
     *  (`<stem>${MARK_SUFFIX}`), so the pair can never drift onto different keys. */
    private basenameFor;
    private markPathFor;
    private writeMarkFile;
    /** The mark as given — the erase's lineage transition rewrites a mark keeping whatever attribution it had. */
    private writeMark;
    /** The keyed read: `null` is ABSENCE only (no file); a file that is there is served as rules or refused
     *  ({@link refuseDamaged}) — it is never read as "no rules". */
    private read;
    /** One row file → its JSON object, the fault that makes it not one (the read failure included), or `null` for
     *  ABSENCE (ENOENT — also a row unlinked under a sweep, an honest absence). Every face reads a row through here. */
    private readRowFile;
    getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
    putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
    listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
    deleteBySession(sessionId: string): Promise<void>;
    /** Erase ONE row this session owns: the number must outlive it, so it is folded into the key's mark BEFORE the
     *  unlink, and the erase ends the row's lineage (`__gen` + 1) in that same single mark write — a crash in between
     *  leaves a mark one erase ahead of a row that still exists, which the next erase counts again (a skipped
     *  generation, and nothing else). Normally the mark is already ahead (every stamp writes it first); the fold earns
     *  its keep on rows that predate the mark. A row is released only once its number is provably safe, so exactly ONE
     *  fault may be absorbed: bytes that are not a mark. Those already block every future write to this key (`putRules`
     *  refuses on them), so they are left as they are rather than replaced by evidence this sweep cannot check. A mark
     *  that cannot be READ proves nothing: {@link refuseUnreadable}, and the erase is retried. `observed` = the read the
     *  owner judgment already took — acted on as observed, so a mark cannot vanish between the judgment and the erase. */
    private eraseRow;
}
