/**
 * CommitmentsSync — P1.5a of multi-machine coherence: the serve / receive /
 * merge engine behind the `commitments-sync` mesh verb.
 *
 * Spec: docs/specs/COMMITMENTS-COHERENCE-SPEC.md §3.2 (read replication) +
 * §3.3 (the merged read). Transport-agnostic, seam-injected (the server
 * registers the serve side as the verb handler; the receive side rides the
 * PeerPresencePuller cadence like journal-sync).
 *
 * Load-bearing rules (§3.2):
 *  - Pages are seq-windowed DELTAS: records with lastMutatedSeq > sinceSeq,
 *    ordered (lastMutatedSeq asc, id tiebreak), EXCLUSIVE cursor, capped at
 *    `syncPageBytes` per page with at least one record per page — a large
 *    store replicates fully over multiple requests, never a flat blob whose
 *    tail stays permanently unreplicated.
 *  - Incarnation fencing: a request carrying a stale incarnation is answered
 *    `incarnationChanged` — the receiver discards the replica wholesale and
 *    re-pulls from 0 (a restored backup must never strand replication).
 *  - First-hop with teeth: the replica's owner identity derives from the
 *    AUTHENTICATED envelope sender; any served row whose originMachineId
 *    names a DIFFERENT machine is rejected + counted (forgedRows) — a peer
 *    cannot inject rows attributed to (or routable to) third machines.
 *  - Disclosure honesty: free-text fields are credential-shape-scanned at
 *    serve time; a flagged FIELD ships REDACTED (the record still
 *    replicates — closeability never depends on the scan).
 *  - Merge: composite key (originMachineId, id) — ids are per-machine
 *    sequential counters and collide across machines by construction.
 */
import type { Commitment } from '../monitoring/CommitmentTracker.js';
export declare const DEFAULT_SYNC_PAGE_BYTES: number;
export declare const DEFAULT_REPLICA_STALE_WARN_MS: number;
export interface CommitmentsSyncRequest {
    sinceSeq: number;
    incarnation?: string;
}
export interface CommitmentsSyncPage {
    incarnation: string;
    replicationSeq: number;
    /** Served records (redacted + origin-stamped). Empty when caught up. */
    records: ReplicatedCommitment[];
    /** EXCLUSIVE cursor for the next request. */
    nextSinceSeq: number;
    /** True when no records remain past nextSinceSeq. */
    done: boolean;
    /** Set when the requester's incarnation is stale — re-pull from 0. */
    incarnationChanged?: boolean;
}
/** A commitment as it travels: always origin-stamped, possibly redacted. */
export interface ReplicatedCommitment extends Commitment {
    originMachineId: string;
    textRedacted?: boolean;
}
export interface ServePageDeps {
    ownMachineId: string;
    /** The OWN store's records (CommitmentTracker.getAll()) — never replicas. */
    records: Commitment[];
    advert: {
        incarnation: string;
        replicationSeq: number;
    };
    syncPageBytes?: number;
}
export declare function buildCommitmentsSyncPage(req: CommitmentsSyncRequest, deps: ServePageDeps): CommitmentsSyncPage;
export interface ApplyResult {
    applied: number;
    /** Rows claiming an originMachineId ≠ the authenticated sender — rejected. */
    forgedRows: number;
    /** The replica was discarded wholesale (incarnation change). */
    replaced: boolean;
}
/**
 * The per-peer replica store: one JSON file per owner under
 * `state/commitment-replicas/`, written ONLY by this receive path
 * (single-writer), temp-file + atomic rename, corrupt → quarantine +
 * fresh (never silently empty).
 */
export declare class CommitmentReplicaStore {
    private readonly dir;
    private readonly now;
    private readonly logger;
    private cache;
    constructor(config: {
        stateDir: string;
        now?: () => Date;
        logger?: (msg: string) => void;
    });
    /** The cursor to request next from a peer (0 + no incarnation when fresh). */
    cursorFor(ownerMachineId: string): {
        sinceSeq: number;
        incarnation?: string;
    };
    /**
     * Apply a served page. `senderMachineId` is the AUTHENTICATED envelope
     * sender — the replica identity derives from it, never a payload field.
     */
    applyPage(senderMachineId: string, page: CommitmentsSyncPage): ApplyResult;
    /** Every replica's rows, tagged with owner + receivedAt. */
    allReplicas(): {
        ownerMachineId: string;
        receivedAt: string;
        records: ReplicatedCommitment[];
    }[];
    private fileFor;
    private load;
    private persist;
}
export interface MergedCommitmentRow extends Commitment {
    /** Named viewSource — Commitment.source ('sentinel'|'agent'|'manual') is taken. */
    viewSource: 'own' | 'replica';
    originMachineId: string;
    /** Replica rows only: now − replica.receivedAt. */
    stalenessMs?: number;
    /** Computed at merge time from the pending-mutation ledger — never stored. */
    pendingMutation?: boolean;
    /** Heuristic duplicate signal (§3.3a) — composite keys of suspects. */
    possibleDuplicateOf?: string[];
}
export interface MergeDeps {
    ownMachineId: string;
    own: Commitment[];
    replicas: {
        ownerMachineId: string;
        receivedAt: string;
        records: ReplicatedCommitment[];
    }[];
    /** Pending-mutation join (P1.5b) — composite keys with in-flight mutations. */
    pendingKeys?: Set<string>;
    now?: () => Date;
}
export declare function compositeKey(originMachineId: string, id: string): string;
/**
 * Own + replicas on the composite key. Own records are authoritative for
 * own truth (replica copies of OUR records never appear — only rows whose
 * origin is the replica's owner survive the first-hop filter, so the only
 * possible overlap is none by construction).
 */
export declare function mergeCommitmentViews(deps: MergeDeps): MergedCommitmentRow[];
/**
 * §3.1 — bare-id resolution over the merged view: exactly one match → the
 * row; several → 'ambiguous' (the route answers 409 listing candidates);
 * none → null.
 */
export declare function resolveBareId(rows: MergedCommitmentRow[], id: string, origin?: string): MergedCommitmentRow | 'ambiguous' | null;
//# sourceMappingURL=CommitmentsSync.d.ts.map