import type { MemoryEntry, ScoredMemoryEntry } from "./types.js"; /** * design/142 §3 — the per-replica per-scope sync cursor: the COMMON BASELINE a reconciliation run * judges divergence against (`id → rev` as of the last completed sync round). This is a NEW * persistence face — deliberately NOT revs.json (that is the local disk-vs-committed ledger): File * plane = a control-plane sidecar, DB plane = a service-owned table (PG+TiDB, [628] dual-dialect). * Serializable by construction. */ export interface MemorySyncCursor { scope: string; /** The PEER this cursor tracks (e.g. "central", a repo remote name) — one cursor per peer. */ peer: string; /** id → rev at the last completed sync round with this peer. */ baseRevs: Record; /** Caller-injected clock (never Date.now inside the engine — clock-injection precedent). */ updatedAtMs: number; } export interface MemorySyncConflict { id: string; /** Present when the side still has the entry (absent = deleted on that side). */ local?: MemoryEntry; remote?: MemoryEntry; baseRev?: string; } export interface MemorySyncPlan { /** Entries the LOCAL side should send to the peer (new here or changed-here-only). 独立轨 F8 — * CAS discipline for the peer write: a push item exists ONLY when the peer is still at the * baseline, so the peer-side conditional write uses `baseRevs[id]` as its baseRev (absent from * the baseline ⇒ the peer has no such entry ⇒ plain add). Never write the peer unconditionally. */ push: MemoryEntry[]; /** Entries the LOCAL side should adopt from the peer (new there or changed-there-only). Same CAS * discipline as {@link push}, mirrored: local conditional write with `baseRevs[id]`. */ pull: MemoryEntry[]; /** codex 复审 B4 — DELETE propagation (without these the removal never crosses and every round * re-plans the same state = livelock): the peer removed an entry the local side left UNCHANGED * since baseline ⇒ delete locally (CAS on baseRev — a local edit racing in makes the delete a * conflict on the next round instead of silent loss). */ deleteLocal: Array<{ id: string; baseRev: string; }>; /** Symmetric: locally removed, peer unchanged since baseline ⇒ delete on the peer. */ deleteRemote: Array<{ id: string; baseRev: string; }>; /** Both sides diverged from the baseline (or delete-vs-edit races): the caller resolves via the * §3 ladder — winner by the caller's policy, LOSER MINTED AS A SIBLING entry (never dropped), * the pair recorded so a three-way exchange cannot rebuild the same conflict forever. */ conflicts: MemorySyncConflict[]; /** Ids deleted on BOTH sides since the baseline (pure cursor cleanup, no data movement). */ cleared: string[]; } /** * Pure three-way set reconciliation over one scope. `baseRevs` = the persisted common baseline * (empty object = first sync round: everything present is "new" on its side; identical revs on * both sides collapse to no-op). * * Judgment per id (rev lineage, mtime never consulted): * - both sides at baseline rev → no-op; identical revs (any lineage) → no-op + baseline advance; * - changed on exactly ONE side → push/pull; * - changed on BOTH sides, or delete-on-one × edit-on-the-other → conflict (caller's ladder); * - deleted on one side × UNCHANGED on the other → the delete propagates (deleteLocal/deleteRemote, * CAS on baseRev — codex B4: without this leg removals never cross and the plan livelocks); * - present only on one side with NO baseline → new entry → propagate; * - absent on both but present in baseline → cleared (cursor cleanup). */ export declare function reconcileMemoryEntries(baseRevs: Readonly>, local: readonly MemoryEntry[], remote: readonly MemoryEntry[]): MemorySyncPlan; /** The baseline to persist AFTER a sync round completes: every id both sides now agree on. The * caller passes the POST-round entry sets (after applying push/pull and resolving conflicts). */ export declare function nextSyncBaseline(local: readonly MemoryEntry[], remote: readonly MemoryEntry[]): Record; /** * A third-party semantic recall side-channel (Mem0/Zep/Cognee/… adapters — research 2026-07-12: * all five surveyed engines fit THIS seat; none fits the authority seat). STRICTLY READ-ONLY by * construction: a RecallSource feeds candidate hits into injection-time recall; it never sees the * write path, never participates in harvest, and the system runs identically without it. * * Score contract: DISTANCE in [0, 2] (0 = identical, 2 = orthogonal — the vector-rung convention); * adapters normalize their similarity/relevance scores into this scale. Returned entries may be * the third party's RE-STATEMENTS (extractive engines rewrite) — that is acceptable HERE precisely * because the seat is advisory: the authoritative body is re-read from the real backend by id/slug * when a hit is followed. Mount point: a deployment composes its RecallSource(s) inside its recall * select hook (the design/65 selective-recall trust seat) or pre-queries before prepare; core adds * no RunnerDeps seat until a real integration signals the need (YAGNI, [622]③ two-lane ruling). */ export interface RecallSource { /** Stable adapter name (diagnostics/attribution: hits are labeled, never silently mixed). */ name: string; search(query: string, scopes: readonly string[], opts?: { limit?: number; }): Promise; } /** Merge recall hits from multiple sources with the authority backend's own results: id-deduped * (FIRST occurrence wins — callers order sources by trust, authority first), ascending by score * (distance), capped at `limit`. Pure — no I/O, no source calls. */ export declare function mergeRecallHits(hitLists: ReadonlyArray, limit: number): ScoredMemoryEntry[]; //# sourceMappingURL=sync.d.ts.map