/**
 * TopicProfileTransferCarrier — the §5.3 transfer-follow carrier
 * (TOPIC-PROFILE-SPEC §5.3, "pull-at-ACQUIRE — a named prerequisite sub-task").
 *
 * A Topic Profile is STICKY OPERATOR INTENT and must follow the topic across a
 * session-pool move. Verified on target: `POST /pool/transfer` carries no
 * payload and has no receive-side hook, and topics also change machines
 * through paths that never run the planner (hard failover, quota-aware
 * placement, lease movement). So the carriage hook lives at the
 * **ownership-ACQUIRE chokepoint**: when THIS machine acquires a topic, it
 * pulls the topic's profile entry from the previous owner over the existing
 * Ed25519 machine-auth channel (the `topic-profile-pull` MeshRpc verb) —
 * covering planner transfers, failovers, and placement moves with ONE
 * mechanism.
 *
 * Spec discipline implemented here:
 *  - **Batch + latency bounds** (§5.3 round-5 scalability): acquires arriving
 *    within the batching window coalesce into ONE pull per previous-owner
 *    peer carrying all topics acquired from it; the durable retry is keyed
 *    per (peer, batch) with backoff — never N independent per-topic retries.
 *    Defaults-with-disclosure notices aggregate into one summary per landing.
 *  - **Fully async**: `onTopicAcquired` is fire-and-forget — the spawn NEVER
 *    waits (the strong form of the §5.3 ~1-2s budget); reconcile-on-pull-
 *    landing handles late arrival.
 *  - **Unreachable previous owner**: the pull is filed DURABLY
 *    (`state/topic-profile-pending-pulls.json`) before the first attempt, so
 *    a crash/restart never loses it; while it is pending, resolution keeps
 *    using the LOCAL entry (`hasPendingPull` is the staleness signal for the
 *    resolution layer's disclosure).
 *  - **Late pull never clobbers a fresher local operator write** (§5.3
 *    rounds 5-8, event-ordered and clock-free): a local operator (or
 *    token-trust HTTP) write DURABLY AMENDS the pending-pull record itself —
 *    removes that topic from the (peer, batch) retry entry — via
 *    `onLocalWriteDurable`, which the write surface calls ONLY after the
 *    triggering mutate's flush has durably landed (`store.mutate` resolves
 *    only then, so "after await mutate" IS the contract; a flush-refused
 *    write throws and cancels nothing). The amendment survives restarts
 *    exactly as well as the pull it cancels. The apply path re-reads the
 *    durable record at landing time, so a write racing an in-flight pull
 *    cancels the REPLACE too. System-attributed writes NEVER cancel.
 *  - **updatedAt backstop** (clock-skew-aware, for pulls landing with no
 *    recorded local write event): local newer wins, ties favor local, and
 *    the pulled entry's updatedAt is clamped to ≤ the pull-receipt time for
 *    the comparison (a future-dated pulled entry is treated as older,
 *    audited). System-attributed local writes never supersede via the
 *    backstop (§5.3 round-6 — the breaker must not shed a transferred pin
 *    through a side door).
 *  - **Newly-pending supersedes older pending** for the same topic (rapid
 *    A→B→C→B re-transfers can't leave two pulls racing).
 *  - **protocol-unsupported ≠ unreachable**: a peer answering `no-handler`
 *    (its instar predates the verb) PARKS the record until
 *    `peerSupportsPull` reports support — no backoff spin against a
 *    permanent 404.
 *  - **Absent on the previous owner clears nothing** — the local entry stays
 *    (a failover-then-transfer-back must not wipe a still-valid pin).
 *  - **Receiving-machine revalidation is MANDATORY**: every landing goes
 *    through `store.replaceEntry(..., { revalidate })` (§10.2 closed-enum +
 *    framework-compat clamp; dropped fields disclosed). Provenance travels
 *    verbatim but the audit tags the landing `origin:'transfer:<machineId>'`
 *    — peer-asserted, never a locally-verified principal.
 *
 * The serve side (`createTopicProfilePullHandler`) is the pool-layer handler
 * the dispatcher registers for the `topic-profile-pull` verb: it serves this
 * machine's OWN entries (current + dry-run shadow — `previous` stays local,
 * undo means "back to what this machine had").
 */
import type { IntelligenceFramework } from './intelligenceProviderFactory.js';
import type { IntendedProfileShadow, TopicProfile, TopicProfileStore } from './TopicProfileStore.js';
import type { EscalationHint } from './EscalationHintStore.js';
export interface TopicProfilePullEntry {
    topicKey: string;
    /** False when the previous owner has NO entry — the receiver clears nothing. */
    present: boolean;
    current: TopicProfile | null;
    /** The §14 dry-run shadow travels verbatim (revalidated on receipt). */
    intendedProfile: IntendedProfileShadow | null;
    /**
     * WS5.3 (escalation-rides-topic) — an OPTIONAL ephemeral escalation hint the
     * source filed when this topic was moved while running on the escalated
     * tier. It rides the SAME authenticated acquire pull (so it crosses N
     * machines without a new verb) but is NOT part of the durable profile: the
     * receiver consumes it to RE-ADMIT through its OWN governor, never to write a
     * pin. Absent unless WS5.3 is enabled on the source AND the topic was
     * escalated. A trigger carry, NEVER a tier grant. */
    escalationHint?: EscalationHint | null;
}
export type TopicProfilePullResponse = {
    ok: true;
    entries: TopicProfilePullEntry[];
} | {
    ok: false;
    reason: string;
};
/** Upper bound on topics served per pull (a mass failover stays one request). */
export declare const MAX_TOPICS_PER_PULL = 500;
/**
 * The serve-side handler for the `topic-profile-pull` MeshRpc verb — pool
 * layer registers it on the dispatcher. Stateless; reads the authoritative
 * in-memory cache (O(topics)). Absent entries answer `present:false` so the
 * receiver can distinguish "no entry" (clear nothing) from a null profile.
 */
export declare function createTopicProfilePullHandler(deps: {
    store: Pick<TopicProfileStore, 'get'>;
    maxTopicsPerPull?: number;
    /**
     * WS5.3 — optional peek of the source's ephemeral escalation hint for a
     * topic (the EscalationHintStore.peek surface). Undefined ⇒ WS5.3 not wired
     * on this machine ⇒ the entry carries no hint (back-compat). The peek does
     * NOT consume — a pull may be retried; the source hint expires by its own
     * TTL, and the destination re-admit is governor-gated so a duplicate is a
     * harmless idempotent admit. */
    escalationHintPeek?: (topicKey: string) => EscalationHint | null;
}): (cmd: {
    type: 'topic-profile-pull';
    topics: unknown;
}) => TopicProfilePullResponse;
/** Outcome of one transport attempt — the wiring maps MeshRpcClient results. */
export type SendPullOutcome = {
    kind: 'ok';
    entries: TopicProfilePullEntry[];
}
/** The peer's instar predates the verb (dispatcher `no-handler`) — PARK. */
 | {
    kind: 'protocol-unsupported';
} | {
    kind: 'unreachable';
    detail?: string;
};
/** Origin of a local profile write, for the §5.3 cancel-marker rules. */
export type LocalWriteOrigin = 'operator' | 'http' | 'system';
interface PendingPullRecord {
    episodeId: string;
    peer: string;
    topics: string[];
    attempts: number;
    /** Epoch ms — due when <= now. 0 = due immediately. */
    nextRetryAt: number;
    /** Parked until the peer's protocolVersion handshake reports support. */
    parkedForProtocol: boolean;
    createdAt: string;
}
export interface TopicProfileTransferCarrierDeps {
    /** Absolute path to the agent's `.instar/` directory (ledger location). */
    stateDir: string;
    selfMachineId: string;
    store: TopicProfileStore;
    /** Default framework — the §10.2 fallback the arriving model validates against. */
    effectiveFramework: () => IntelligenceFramework;
    /** Live ownership read — the ONLY actuation authority at apply time. */
    ownerOf: (topicKey: string) => {
        owner: string | null;
    };
    /**
     * Previous-owner evidence (journal placement history) for acquires whose
     * hook could not name the previous owner. Null = unknown → nothing to pull.
     */
    prevOwnerOf?: (topicKey: string) => string | null;
    /** ONE batched transport attempt. May throw (treated as unreachable). */
    sendPull: (peerMachineId: string, topics: string[]) => Promise<SendPullOutcome>;
    /**
     * §5.3 rolling-update skew: does the pool's protocolVersion handshake
     * report the peer supports the pull verb? Undefined = unknown → attempt
     * (the `no-handler` answer parks the record anyway).
     */
    peerSupportsPull?: (peerMachineId: string) => boolean | undefined;
    /** Structured audit sink (every transition; never message content). */
    audit?: (event: Record<string, unknown>) => void;
    /**
     * Aggregated user-facing disclosure per landing batch ("N topics moved
     * from <machine>; …" + per-field revalidation fallbacks). One summary per
     * (peer, landing) — never per topic (§5.3 round-5).
     */
    notify?: (text: string) => void;
    /**
     * WS5.3 — a pull landing carried an escalation hint for a topic THIS machine
     * now owns. Fire-and-forget: the wiring re-admits the resumed session
     * through the LOCAL EscalationGovernor (a trigger carry, NEVER a tier grant —
     * the governor re-decides under every local cost guard). Only invoked after
     * the ownership recheck confirms this machine owns the topic, so a hint can
     * never drive a re-admit on a non-owner. Undefined ⇒ WS5.3 not wired
     * (back-compat — the hint is simply ignored). */
    onEscalationHintLanded?: (topicKey: string, hint: EscalationHint) => void;
    now?: () => Date;
    /** Acquire-coalescing window (ms). Default 250. */
    batchWindowMs?: number;
    /** Retry backoff start (ms). Default 30s. */
    retryBackoffStartMs?: number;
    /** Retry backoff cap (ms). Default 30min. */
    retryBackoffMaxMs?: number;
    /** Pending-record TTL (ms) — expired records drop with an audit. Default 7d. */
    pendingTtlMs?: number;
    logger?: (msg: string) => void;
}
export declare class TopicProfileTransferCarrier {
    private readonly d;
    private readonly ledgerFile;
    /** Staged acquires per previous-owner peer, awaiting the batch window. */
    private staged;
    private batchTimers;
    /** Single-flight per peer (attempts never overlap per peer). */
    private inFlight;
    private episodeSeq;
    /** Durable ledger cache (loaded lazily, kept in sync with every write). */
    private pulls;
    constructor(deps: TopicProfileTransferCarrierDeps);
    private now;
    /**
     * THIS machine acquired ownership of a topic. Stages a pull from the
     * previous owner; acquires within the batch window coalesce into ONE pull
     * per peer (§5.3 batch bound). Fire-and-forget: returns immediately.
     */
    onTopicAcquired(topicKey: number | string, prevOwnerMachineId?: string | null): void;
    /** Force-drain every staged batch now (tests + shutdown). */
    flushStaged(): Promise<void>;
    private flushStagedPeer;
    /**
     * A LOCAL profile write for `topicKey` has DURABLY landed (the caller
     * invokes this only after `store.mutate(...)` resolved — `mutate` resolves
     * only after its flush is durable, so a flush-refused write throws first
     * and cancels nothing). Operator and token-trust HTTP writes durably amend
     * every pending-pull record to drop the topic — the pending REPLACE is
     * CANCELLED, event-ordered and clock-free. System-attributed writes never
     * cancel (§5.3 round-6 — the breaker can't shed a transferred pin).
     */
    onLocalWriteDurable(topicKey: number | string, origin: LocalWriteOrigin): void;
    /** Is a pull pending for this topic? (Resolution's staleness signal, §5.3.) */
    hasPendingPull(topicKey: number | string): boolean;
    /** Observability: the durable pending records (copies). */
    pending(): PendingPullRecord[];
    /**
     * Slow tick (the server calls this every ~60s): TTL-sweep, then attempt
     * every due record (backoff respected; parked records re-checked against
     * the protocol handshake).
     */
    tick(): Promise<void>;
    /** A peer came back online — drain its pending pulls now (backoff bypassed). */
    onPeerOnline(machineId: string): Promise<void>;
    private attemptPeer;
    private attemptPeerInner;
    private park;
    /**
     * A pull landed. Per topic: the durable record is re-read (a local write
     * that amended it mid-flight cancels that topic's REPLACE), ownership is
     * re-checked (actuation authority), the updatedAt backstop runs, then the
     * §10.2-revalidated wholesale REPLACE applies. Disclosures aggregate into
     * ONE summary per landing.
     */
    private applyLanding;
    private loadLedger;
    /** Apply a mutation to the ledger and persist it durably (tmp+rename). */
    private amendLedger;
}
export {};
//# sourceMappingURL=TopicProfileTransferCarrier.d.ts.map