/** * The device's replica registry (journal spec §1.5, N6). * * A JSON file of device state, never inside a store and never synced: its * path comes from `@cleocode/paths` ({@link resolveSyncReplicaRegistryPath}), * in the machine-local state dir, keyed by the stable device id. Per replica * id it records the store's nonce, where the store lives, and the device-side * high-water mark (`hwm`): the last `replicaSeq` this device persisted per * stream. A store whose own persisted sequence is BELOW the hwm was rolled * back (restored into the same file), which the open pass turns into a * rebind. * * Losing the file (a new device, a wiped state dir) or finding it unreadable * is treated as an empty registry: the open pass re-registers the store and * does not rebind for that alone. The server's sequence check (S4) is the * authority that catches what a lost registry cannot. * * Writes use the file-then-rename pattern, so a crash leaves the old or the * new file, never a torn one, and every write holds a cross-process lock on * the file, so two processes binding stores at once never drop each other's * entry (T12908). * * A replica this device retired at the same path (a replaced or rolled-back * file; a copy retires nothing) keeps its entry with `retiredAt`, `successor`, * the rebind reason and its hwm: those entries are the retire candidates the * signed `retire` transaction of S4 announces (§1.5 "Retirement"; T13109). * * @task T12342 * @task T13109 * @module store/sync/replica-registry */ /** One replica this device has bound. */ export interface ReplicaRegistryEntry { /** The nonce minted with the replica and stored in its `_sync_replica` row. */ readonly nonce: string; /** `project` or `global`. */ readonly scope: string; /** Real path of the store file when last opened here. */ readonly dbRealpath: string; /** Last persisted `replicaSeq` per stream. */ readonly hwm: Readonly>; /** When this replica was retired on this device, if it was. */ readonly retiredAt?: string; /** The replica that replaced it. */ readonly successor?: string; /** Why it was retired: the rebind reason(s), e.g. `vault-restore` or `file-identity`. */ readonly retireReason?: string; /** When S4's signed `retire` transaction announced it; unset while it is still a candidate. */ readonly retireAnnouncedAt?: string; readonly updatedAt: string; } /** A replica this device retired, as the registry records it. */ export interface RetiredReplica { readonly replicaId: string; /** `project` or `global`. */ readonly scope: string; /** Real path of the store file it named. */ readonly dbRealpath: string; /** The replica that replaced it. */ readonly successor: string; readonly retiredAt: string; /** The rebind reason(s), or `null` for an entry retired before reasons were recorded. */ readonly reason: string | null; /** Its last persisted `replicaSeq` per stream: what a `retire` transaction names. */ readonly lastReplicaSeq: Readonly>; /** When S4 announced it, or `null` while it is a candidate. */ readonly announcedAt: string | null; } /** The registry file. */ export interface ReplicaRegistryFile { readonly version: 1; readonly deviceId: string; readonly replicas: Readonly>; } /** Reads and writes one device's registry file. */ export declare class ReplicaRegistry { readonly path: string; readonly deviceId: string; /** * @param path - The registry file. * @param deviceId - The device the file belongs to. */ constructor(path: string, deviceId: string); /** The registry at its canonical path for `deviceId`. */ static forDevice(deviceId: string): ReplicaRegistry; private empty; /** * The registry contents. A missing, unreadable or foreign file (another * device id) reads as empty. */ read(): ReplicaRegistryFile; /** One replica's entry, if registered. */ get(replicaId: string): ReplicaRegistryEntry | undefined; /** Replace the whole file (file-then-rename), under the file's lock. */ write(file: ReplicaRegistryFile): void; private writeLocked; /** * Insert or update one replica's entry. The hwm only moves up. * * @returns Whether the file changed. */ upsert(replicaId: string, patch: Omit & { hwm?: Readonly>; }, now?: Date): boolean; private upsertLocked; /** * Advance a replica's hwm for one stream after a segment persist committed * (S4 calls this right after the store transaction, §2.8). If the process * dies in between, the store is ahead of the registry, which is the safe * direction: only a store BEHIND the registry rebinds. * * @returns Whether the file changed. */ advanceHwm(replicaId: string, stream: string, seq: number, now?: Date): boolean; /** * The replicas this device retired, newest first, optionally only those of * one store file or scope. * * @param filter - Store real path and/or scope to keep. * @returns The retired entries. */ retired(filter?: { dbRealpath?: string; scope?: string; }): RetiredReplica[]; /** * The retire candidates: retired replicas no `retire` transaction has * announced yet. S4's emitter announces each (retired id, successor, last * `replicaSeq`) and records `retireAnnouncedAt` (§1.5 "Retirement"). * * @returns The candidates, newest first. */ retireCandidates(): RetiredReplica[]; } /** * This device's registry for reading only: `null` when the machine has no * stable device id yet. Unlike `getStableDeviceId`, it never mints one, so a * read-only command writes nothing. * * @returns The registry, or `null`. */ export declare function readDeviceRegistry(): ReplicaRegistry | null; //# sourceMappingURL=replica-registry.d.ts.map