/** Every refusal is one of these, so a caller never mistakes it for an I/O blip. */ export declare class SubjectFrontierCorruptError extends Error { readonly path: string; readonly invariant: string; constructor(path: string, invariant: string, detail: string); } /** * The record on disk is not the one this view was opened from, so this writer's number is not the * one to write. * * DISTINCT FROM CORRUPTION: the file is perfectly well formed, it just belongs to a later state * than this object remembers. Both are refusals rather than repairs, and telling them apart is what * lets an operator know whether to look at the filesystem or at a second writer. */ export declare class SubjectFrontierMovedError extends Error { readonly path: string; readonly viewTip: number; readonly diskTip: number | undefined; constructor(path: string, viewTip: number, diskTip: number | undefined); } /** * What the emitter needs from a subject frontier. * * An INTERFACE rather than only a class, so a suite whose subject is something else can pass a * double without a test seam having to exist in shipped code. The one shipped implementation is * {@link FileSubjectFrontier}. */ export interface SubjectFrontier { /** The last sequence assigned on this principal's subject, or 0 if it has never published. */ readonly tip: number; /** Record a newly assigned sequence. Refuses a value that does not advance the tip. */ advance(seq: number): Promise; /** Abandonment: the subject tip genuinely returned to 0, so the record must too. */ reset(): Promise; } /** The durable implementation, one file per principal beside that principal's thread directories. */ export declare class FileSubjectFrontier implements SubjectFrontier { private readonly path; private doc; private constructor(); get tip(): number; /** * Open, or create a virgin record. * * A MISSING file is virgin and legal: this principal has never published, which is the ordinary * state on a first run and after a fresh install. A ZERO-BYTE file is NOT, for the same reason * the write-ahead log refuses one: an atomic temp-and-rename never produces it, so it is a * filesystem that lost the tail, and reading it as "never published" is the guess this whole * mechanism exists to remove. */ static open(path: string, opts: { space: string; principal: string; }): Promise; /** * Bytes to a validated document, or a refusal. * * SHARED BY `open` AND BY THE RE-READ IN {@link advance} on purpose. A record that went corrupt * underneath a live writer has to meet the same wall as one that was corrupt at boot; validating * only on the way in would let a writer that opened a good file overwrite a bad one, which * destroys the evidence of whatever produced it. */ private static parse; advance(seq: number): Promise; /** * The tip the FILE holds, or `undefined` when no record exists yet. * * Fully validated, not a bare `JSON.parse().tip`: the disagreement this feeds is decided on a * number, and a number taken from a document that failed its own shape checks is not evidence. */ private readDiskTip; /** * One mutation at a time on THIS instance. * * The re-read above is a read-modify-write, so two callers that interleave between the read and * the rename would both pass a check neither still satisfies. One frontier is legitimately bound * to SEVERAL logs (the pinning runs the other way: a log may not change which record it * publishes onto), so concurrent callers on one instance are an ordinary state, not a misuse. * * It serializes this instance and nothing else. Two instances have two chains, which is the case * the re-read exists for. */ private chain; private serialize; /** * Recover the tip from the THREAD LOGS beside this record, for an installation upgrading from a * release where this record did not exist. * * **THIS IS THE WHOLE UPGRADE PATH AND LEAVING IT OUT MAKES THE FIX APPLY TO NOBODY WHO ALREADY * RAN THE BROKEN VERSION.** My first attempt seeded from the log of the thread being opened, which * is empty in the case that matters: upgrading restarts the seat, so the first session after the * upgrade is a NEW thread with a virgin log, while the sequence it needs sits in the PREVIOUS * thread's log. A cell in `smoke:agui-multi-session` failed on exactly that and is the reason this * function exists rather than the reasoning that produced the first version. * * **ONLY WHEN THE RECORD IS ABSENT, NEVER WHEN IT READS ZERO.** A record holding zero is what * abandonment writes after a filtered purge, and re-seeding it from a thread log would silently * undo the abandonment and restore an expectation the subject no longer has. Missing and zero are * different states and this is the second place in this plane where conflating them is the bug. * * A sibling that cannot be read or does not parse is FATAL rather than skipped. Skipping it * under-counts the tip, which produces a permanent halt later with a message about a moved tip, * pointing at everything except the file that was quietly ignored here. */ private static recoverTipFromThreadLogs; reset(): Promise; /** Atomic replace: sibling temp, fsync, rename, fsync the directory. */ private write; } //# sourceMappingURL=subject-frontier.d.ts.map