/** Coverage of ONE derived field for one session: the field `version` it was * reconciled at, whether the value was `present` (1) or absent (0 = scanned, * this session has no such field), and the session `mtime` it was scanned at * (so mtime-sensitive fields re-scan when the transcript changes). */ export interface FieldCoverage { v: number; p: 0 | 1; m?: number; } /** A synced row: conversation mtime covered + extractor version it was produced * under, plus per-field coverage `f` for the derived-field reconciliation * (see sync-fields.ts), plus the tail-sync byte offset `o` and file size `s` * (see docs/SYNC-INCREMENTAL.md). The conversation sync owns {m,v,o,s}; field * reconciliation owns `f` — neither path re-does the other's work. */ export interface SyncedRow { m: number; v: number; f?: Record; /** Last byte offset shipped (append-only transcripts). 0 = no tail sync yet * (FULL sync needed or never shipped). >0 = eligible for APPEND from here. */ o?: number; /** File size at the time `o` was captured. Lets the freshness gate detect * growth (append) vs rotation/truncation (shrink → FULL). */ s?: number; /** Content fingerprint (containerSrcHash) of the transcript at the last FULL * sync to this target. Lets the walk skip a session that re-classified as * FULL (mtime bumped / resume rewrite / OpenCode summary bump) but whose * actual content is byte-identical to what was already shipped — no re-parse, * re-redact, KG or git-replay. Only trusted when `v` is the current extractor * version. Absent after an APPEND (the head hash is stale) and on legacy rows. */ h?: string; } /** On disk a row is the {m,v[,f,o,s]} shape OR a legacy bare mtime number. */ type LedgerEntry = SyncedRow | number; /** Where the ledger lives — exposed so repair tooling can back it up before * editing the file that decides what gets uploaded. */ export declare function ledgerFilePath(): string; /** * Drop rows for servers this machine no longer syncs to. * * Nothing ever removed a target key. `removeCredentials` rewrites * credentials.json and leaves the ledger alone, so every server the user ever * pointed at keeps its full row set forever — on this developer's machine four * dead targets (an old hostname, two localhost ports, a stale LAN address) held * 61,331 of 88,765 rows, about 7.6 MB, re-serialized on every single write. * * Orphans are RENAMED into a sidecar, never deleted. A user who logs out of a * server and back in a week later would otherwise re-ship their whole history; * keeping the rows makes that free, and the sidecar is a plain JSON file they * can inspect or remove. * * Returns the number of targets moved out. */ export declare function pruneLedgerTargets(configuredServers: string[]): number; /** * Write any coalesced ledger changes out now. Call at the end of a sync walk and * before exit — anywhere the next thing that happens might be a crash. */ export declare function flushLedger(): void; /** tool → last item-extractor version fully walked for this server. */ export declare function loadItemVersions(server: string): Record; /** Record (merge) the versions this server's items were just walked under. */ export declare function saveItemVersions(server: string, versions: Record): void; /** session_id → mtime the server has acked, for one target server. */ export declare function getSyncedMtimes(server: string): Map; /** * session_id → {mtime, extractorVersion} the server has acked. This is the * accessor the freshness gate uses: a session is current only when the synced * mtime covers the file AND the synced version is >= EXTRACTOR_VERSION. */ export declare function getSyncedRows(server: string): Map; /** Raw data for one server, useful when you need to mutate (e.g. tombstones). */ export declare function getLedgerData(server: string): Record; /** Persist mutated data for one server. */ export declare function persistLedgerData(server: string, serverData: Record): void; export declare function unackedCursorAttemptCount(): number; export declare function _resetUnackedCursorCount(): void; export declare function markSynced(server: string, rows: Array<{ id: string; mtime: number; offset?: number; size?: number; hash?: string; /** The server confirmed a write covering `offset`/`size`. Required for the * cursor to advance; without it those fields are ignored. */ acked?: boolean; }>): void; /** * Does `field` need (re)scanning for a session whose ledger row is `row` and * whose current transcript mtime is `mtime`? True when never covered, covered * at an older field version, or (for mtime-sensitive fields) the transcript * moved past the mtime it was scanned at. `force` overrides everything. An * absent-but-current field returns false — that's the no-retry guarantee. */ export declare function fieldNeedsScan(row: SyncedRow | undefined, field: { name: string; version: number; mtimeSensitive: boolean; }, mtime: number, force?: boolean): boolean; /** * Record per-field coverage after the server acked (or, for absent fields, * locally). Each row stamps ONE field's {version, present, mtime} onto the * session's `f` map WITHOUT touching its {m,v} conversation state or any other * field. Preserves/creates the row as needed. */ export declare function markFieldCoverage(server: string, field: { name: string; version: number; }, rows: Array<{ id: string; present: boolean; mtime: number; }>): void; /** True if a one-time FULL backfill pass is owed for `field` on `server` — * either never reconciled at this version, or a re-scan was forced. */ export declare function fieldNeedsFullPass(server: string, field: { name: string; version: number; }): boolean; /** Mark a field's full backfill pass complete for `server` at `version`, and * clear any force flag. Call only after the whole pass succeeded. */ export declare function markFieldFullPassDone(server: string, field: { name: string; version: number; }): void; /** UI/CLI hook: demand a one-time re-scan of `field` (optionally on one server, * else all known servers). Sets the force flag the next sync honors. */ export declare function forceFieldRescan(fieldName: string, server?: string): void; /** Test-only: drop the in-memory caches so a fresh file read happens next call. */ export declare function _resetLedgerCacheForTests(): void; /** * Invalidate a session's ledger row so the next sync tick classifies it as * FULL (never-synced). Used when the server signals `full_resync_needed` for * an append — the client must not retry append (the server lost the prior * envelope); it must FULL re-sync. Deleting the row (rather than clearing just * o/s) is correct: a FULL re-sync re-derives everything including field * coverage `f`, so there's nothing worth preserving. Preserves other sessions. */ export declare function markFullResync(server: string, sessionId: string): void; export type SyncMode = 'skip' | 'append' | 'full'; /** * Classify one session's sync mode for this tick. * * @param row ledger row for this session (undefined = never synced) * @param mtime current transcript mtime (ms, floored) * @param fileSize current transcript byte size (0 when unknown / not append-only) * @param extractorVersion current EXTRACTOR_VERSION * @param isAppendOnly whether the backend's transcript is an append-only file * (Claude/Gemini/Codex JSONL = true; OpenCode SQLite = false) */ export declare function syncMode(row: SyncedRow | undefined, mtime: number, fileSize: number, extractorVersion: number, isAppendOnly: boolean): SyncMode; export {}; //# sourceMappingURL=sync-ledger.d.ts.map