/**
 * threadSymmetry — the cross-end symmetry state machine + the participant-
 * authorized, terminating convergence backfill (Robustness Phase 2, D-D).
 *
 * Symmetry is ADVISORY-ONLY (Signal vs. Authority): it never blocks a send, never
 * binds, never gates an irreversible action. Divergence is surfaced loudly (ONE
 * deduped Attention item per (thread, episode)) and an upgraded pair can converge
 * via a bounded backfill, but a forged `verified` only misleads an auditor — it
 * grants nothing (SA3 honest bound).
 *
 * Authorization is the keystone (SA1): every participant check keys on the
 * fingerprint DERIVED from the verified Ed25519 signature — NEVER a name header or
 * a body/envelope `from` field. A representation mismatch fails CLOSED.
 */
import { type ThreadSync } from './threadDigest.js';
import type { ThreadLog, ThreadDirection } from './ThreadLog.js';
import type { ConversationStore } from './ConversationStore.js';
import type { ThreadMessageRecorder } from './recordThreadMessage.js';
/** The closed set of advisory symmetry states (FD-6). Only the diverged-* states are actionable. */
export type SymmetryState = 'verified' | 'diverged' | 'diverged-unreconcilable' | 'version-skew' | 'unverified-peer-legacy' | 'unverified-backfill' | 'local-integrity-fault' | 'unknown';
/** The dependency bundle the symmetry surfaces read (a subset of CanonicalHistoryDeps). */
export interface SymmetryDeps {
    threadLog?: ThreadLog;
    conversationStore?: ConversationStore;
    threadMessageRecorder?: ThreadMessageRecorder;
    backfillMaxDigestsPerRequest?: number;
    backfillMaxRecordsPerResponse?: number;
    attention?: {
        createAttentionItem?: (item: {
            id: string;
            title: string;
            summary: string;
            category: string;
            priority: 'URGENT' | 'HIGH' | 'NORMAL' | 'LOW';
            sourceContext?: string;
        }) => unknown;
    } | null;
    /**
     * Gated (dev-live/fleet-dark) active backfill pull. Present ONLY when the
     * convergence-backfill feature is enabled. On a confirmed `diverged`, ONE bounded
     * round is initiated: this posts an authenticated backfill request to the peer
     * (returning its records) — the caller recomputes + ingests them (SA4). Absent →
     * divergence is detect-and-surface only (the core fleet posture).
     */
    backfillInitiator?: (threadId: string, missingDigests: string[]) => Promise<BackfillRecord[]>;
}
/** A backfill wire record — the requester recomputes EVERYTHING else (SA4). */
export interface BackfillRecord {
    messageId: string;
    body: string;
    createdAt: string;
    direction: ThreadDirection;
}
/**
 * Derive the advisory symmetry state from the local head + a (verified-participant)
 * peer threadSync. Pure — the caller persists it.
 */
export declare function computeSymmetryState(opts: {
    localCount: number;
    localSetAccum: string;
    peerSync?: {
        digestVersion: number;
        count: number;
        setAccum: string;
    } | null;
    hasBackfilled: boolean;
    localVerifyOk: boolean;
    /** Already terminal-unreconcilable → stays sticky terminal (SA2). */
    sticky?: boolean;
    /** Peer is upgraded (sent threadSync at least once) but this report is absent. */
    peerEverReported?: boolean;
}): SymmetryState;
/** Build this end's threadSync for a thread (the additive wire field). */
export declare function localThreadSync(threadLog: ThreadLog, threadId: string): ThreadSync;
/**
 * Honor a peer's piggybacked threadSync: participant-scoped (SA1) + monotonic
 * (anti-replay), then recompute + persist the advisory symmetry state. A threadSync
 * for a NON-participant thread is dropped, never surfaced. Returns the new state.
 */
export declare function honorPeerThreadSync(deps: SymmetryDeps, threadId: string, verifiedFp: string, peerSync: ThreadSync): Promise<SymmetryState>;
/**
 * Serve a participant-authorized, bounded backfill response (SA1). Returns records
 * ONLY for a thread the verified requester PARTICIPATES in, and only for
 * contentDigests already in that thread's log. A non-participant request returns
 * EMPTY (the caller counts the refusal — closes cross-thread exfiltration).
 */
export declare function serveBackfill(deps: SymmetryDeps, threadId: string, verifiedFp: string, missingDigests: string[]): BackfillRecord[];
/**
 * Ingest a backfill RESPONSE — UNTRUSTED (SA4). Each record is treated as raw
 * message content: the requester RECOMPUTES `contentDigest` locally, assigns its
 * OWN seq/prevHash/hash via the funnel, stamps `backfilled:true` + the verified
 * responder as author/peer ITSELF, and IGNORES any peer-supplied chain fields. A
 * record whose recomputed digest is NOT among `requestedDigests` is dropped +
 * counted (a responder cannot push unrequested content). Returns the tally.
 */
export declare function ingestBackfill(deps: SymmetryDeps, threadId: string, verifiedResponderFp: string, records: BackfillRecord[], 
/** The digests this side actually requested. `null` = anti-entropy (accept any). */
requestedDigests: Set<string> | null): {
    ingested: number;
    dropped: number;
};
/**
 * Raise the ONE deduped divergence Attention item (Bounded Notification Surface).
 * The dedup id is stable per thread, so a peer oscillating its claimed digest
 * collapses to one episode.
 */
export declare function raiseDivergenceAttention(deps: SymmetryDeps, threadId: string, terminal: boolean): void;
//# sourceMappingURL=threadSymmetry.d.ts.map