/**
 * PeerPresencePuller (Multi-Machine Session Pool — HTTP presence transport).
 *
 * The pool registry's notion of which peer machines are ONLINE was originally
 * fed only by the git-synced MachineHeartbeat: each machine writes its heartbeat
 * into the shared agent repo, peers pull it, and recency ⇒ online. That breaks
 * for a *credential-less standby* — a machine paired into the mesh over HTTP but
 * WITHOUT push access to the shared repo (e.g. a second machine gh-authed to a
 * different account). Its heartbeat never reaches the router, so the router
 * marks it offline and the placement engine refuses to transfer to it, even
 * though the two machines can reach each other perfectly over their tunnels.
 *
 * This puller closes that gap with a pull-based HTTP presence channel: on a
 * cadence, ask each reachable peer for its self-capacity over the signed
 * /mesh/rpc `session-status` command (a read-class command any registered peer
 * may issue) and record the answer into the local pool registry. A peer that
 * answers is, by definition, reachable + alive — so it goes online without ever
 * touching git. Symmetric by design: every mesh machine runs one, so each keeps
 * its own HTTP-sourced view of peer liveness, parallel to (and idempotent with)
 * the git-synced path for credentialed peers.
 *
 * Fully injected (peers, fetch, record, clock) so the loop logic is unit-testable
 * without a live mesh. `pullOnce()` NEVER throws — an unreachable peer simply is
 * not recorded this pass and ages out of `online` naturally via the registry's
 * failover threshold.
 */
/** A peer to consider polling: its machine id + resolved tunnel URL (null ⇒ unreachable). */
export interface PeerPresenceMachine {
    machineId: string;
    url: string | null;
}
/** The slice of a peer's self-capacity the puller records (from `session-status`). */
export interface PeerCapacity {
    selfReportedLastSeen?: string;
    loadAvg?: number;
    /**
     * The peer's OWN-stream coherence-journal advert (COHERENCE-JOURNAL-SPEC §3.4
     * rule 5), keyed `kind → { incarnation, lastSeq }`. Present only when the peer
     * runs the journal-sync transport; old peers omit it and the delta drive is a
     * no-op. Replication-gated: the puller only acts on it when the delta deps are
     * wired (server passes them ONLY when replication is explicitly enabled).
     */
    journalAdvert?: Record<string, {
        incarnation: string;
        lastSeq: number;
    }>;
    /**
     * The peer's OWN commitments-store advert (COMMITMENTS-COHERENCE-SPEC
     * §3.2). Present only when the peer runs the commitments-sync layer; old
     * peers omit it and the drive is a no-op.
     */
    commitmentsAdvert?: {
        incarnation: string;
        replicationSeq: number;
    };
    /**
     * The peer's OWN preferences-store advert (MULTI-MACHINE-SEAMLESSNESS-SPEC
     * §WS2.1). Present only when the peer runs the preferences-sync layer; old
     * peers omit it and the drive is a no-op.
     */
    preferencesAdvert?: {
        incarnation: string;
        replicationSeq: number;
    };
    /**
     * The peer's self-reported LLM-account quota state (live-matrix finding A2,
     * 2026-06-06). It IS carried in the peer's session-status response (its
     * getCapacity(self) already includes it) but was being PARSED AWAY on the
     * receive side — so the router only ever saw its OWN quota and quota-aware
     * placement (#804) could never avoid a rate-limited PEER (the original EXO
     * failure). Absent from old peers = treated as not blocked (fail-open).
     */
    quotaState?: {
        blocked: boolean;
        blockedUntil?: string;
        reason?: string;
    };
    /**
     * The peer's compact guard-posture summary (GUARD-POSTURE-ENDPOINT-SPEC
     * §2.3). Carried in the peer's session-status response (its
     * getCapacity(self) includes it); the A2 lesson above applies verbatim —
     * narrowing it away on receive would blind the pool view to every peer's
     * posture. Absent from old peers = no posture ("guards: unknown").
     */
    guardPosture?: import('./types.js').GuardPostureSummary;
    /**
     * The peer's self-advertised seamlessness capabilities (MULTI-MACHINE-
     * SEAMLESSNESS-SPEC invariant 5), the carrier of `stateSyncReceive` — which
     * replicated kinds the peer can durably RECEIVE. Carried in the peer's
     * session-status response (its getCapacity(self) includes it via assemble);
     * the #930/A2/WS2.1 narrowing lesson applies a FOURTH time — dropping it here
     * makes the flag-coherence gate read every peer as "cannot receive" and
     * blocks cross-machine replication in BOTH directions (root-caused live on
     * Laptop↔Mini, 2026-06-14). Absent from old peers = non-participant (the
     * conservative side). See `narrowSessionStatusToPeerCapacity` below — the
     * single pass-through both production and the round-trip test share.
     */
    seamlessnessFlags?: import('./types.js').MachineCapacity['seamlessnessFlags'];
    /** Platform/workspace reachability (placement-platform-workspace-aware) — replicated like the others. */
    servesChannels?: import('./types.js').MachineCapacity['servesChannels'];
}
/**
 * The advert fields a peer's `session-status` response can carry that the
 * receive mapping MUST pass through. A SINGLE source of truth shared by
 * `narrowSessionStatusToPeerCapacity` and the wiring-integrity ratchet test, so
 * "added a new advert field to the sender without a pass-through" fails LOUDLY
 * (the #930/A2/WS2.1/seamlessnessFlags class — a narrowing return that forgets a
 * field, now four instances deep). Add a new advert field here and the ratchet
 * covers it automatically.
 */
export declare const SESSION_STATUS_ADVERT_FIELDS: readonly ["journalAdvert", "commitmentsAdvert", "preferencesAdvert", "quotaState", "guardPosture", "seamlessnessFlags", "servesChannels"];
/**
 * Narrow a peer's raw `session-status` response into the `PeerCapacity` slice
 * the puller records — the ONE pass-through both the production `fetchPeerCapacity`
 * (src/commands/server.ts) and `peer-presence-roundtrip.test.ts` run, so the test
 * proves the REAL mapping, not a hand-copied mirror that can silently drift from it
 * (the integration-fidelity gap this extraction closes). Presence guards (`!== undefined`),
 * never truthiness, so the "present even when every sub-flag is false/empty" invariant
 * the seamlessnessFlags carry-forward depends on can't be defeated by a normalized-falsy value.
 * `journalAdvert` is passed in ALREADY UNWRAPPED (the caller owns the machine-id-keyed
 * unwrap, which needs closure context the puller doesn't carry).
 */
export declare function narrowSessionStatusToPeerCapacity(raw: unknown, unwrappedJournalAdvert?: Record<string, {
    incarnation: string;
    lastSeq: number;
}>): PeerCapacity | null;
/** One stream slice of a journal-sync delta (mirrors MeshRpc's `journal-sync.batch`). */
export interface JournalDeltaStream {
    kind: string;
    incarnation: string;
    entries: unknown[];
    oldestRetainedSeq?: number;
}
export interface PeerPresencePullerDeps {
    /** This machine's id — never pulled (a machine doesn't poll itself). */
    selfMachineId: string;
    /** The current mesh peers (id + resolved URL). Re-read each pass so newly paired peers are picked up. */
    listPeers: () => PeerPresenceMachine[];
    /**
     * Send a signed `session-status` read to a peer and resolve its self-capacity,
     * or `null` if the peer did not answer / rejected. May reject; `pullOnce`
     * treats a rejection identically to a `null` (peer not recorded this pass).
     */
    fetchPeerCapacity: (machineId: string, url: string) => Promise<PeerCapacity | null>;
    /** Record an observed peer heartbeat into the pool registry (marks it online for the failover window). */
    recordHeartbeat: (obs: {
        machineId: string;
        selfReportedLastSeen: string;
        loadAvg?: number;
        quotaState?: {
            blocked: boolean;
            blockedUntil?: string;
            reason?: string;
        };
        guardPosture?: import('./types.js').GuardPostureSummary;
        seamlessnessFlags?: import('./types.js').MachineCapacity['seamlessnessFlags'];
        servesChannels?: import('./types.js').MachineCapacity['servesChannels'];
    }) => void;
    /** Wall clock — injectable for tests. Defaults to `Date`. */
    now?: () => Date;
    /** Optional structured log line per pass (e.g. for the boot log). */
    log?: (line: string) => void;
    /**
     * Request a journal delta for `kind` from a peer (a signed `journal-sync`
     * request → the peer's served own-stream batch), or null if unavailable.
     * MUST NOT throw into the puller (the puller treats a throw as null).
     */
    requestJournalDelta?: (machineId: string, url: string, kind: string, fromSeq: number) => Promise<JournalDeltaStream | null>;
    /** Apply a served delta into the local replica (delegates to JournalSyncApplier.apply). */
    applyDelta?: (senderMachineId: string, batch: JournalDeltaStream[]) => void;
    /**
     * What this machine ALREADY holds for `machineId` per kind (from the applier's
     * advert state). Used to decide whether the peer's advert is ahead of us.
     * Returns `{ kind → { incarnation, lastSeq } }` (possibly empty).
     */
    localAdvertFor?: (machineId: string) => Record<string, {
        incarnation: string;
        lastSeq: number;
    }>;
    /**
     * Fired once per peer recorded online this pass (WORKING-SET-HANDOFF-SPEC
     * §3.4 — the pending-pull re-arm rides the same cadence journal-sync does).
     * MUST NOT throw into the puller; consumers dedupe/stagger themselves.
     */
    onPeerRecorded?: (machineId: string) => void;
    /**
     * REPLICATION-GATED commitments-sync drive (COMMITMENTS-COHERENCE-SPEC
     * §3.2): called with the peer's commitments advert when present; the
     * consumer compares against its replica cursor and pulls delta pages
     * (bounded per tick). MUST NOT throw into the puller.
     */
    driveCommitmentsSync?: (machineId: string, url: string, advert: {
        incarnation: string;
        replicationSeq: number;
    }) => Promise<void>;
    /**
     * REPLICATION-GATED preferences-sync drive (MULTI-MACHINE-SEAMLESSNESS-SPEC
     * §WS2.1): called with the peer's preferences advert when present; the
     * consumer compares against its replica cursor and pulls delta pages
     * (bounded per tick). MUST NOT throw into the puller.
     */
    drivePreferencesSync?: (machineId: string, url: string, advert: {
        incarnation: string;
        replicationSeq: number;
    }) => Promise<void>;
}
export declare class PeerPresencePuller {
    private readonly d;
    constructor(d: PeerPresencePullerDeps);
    /**
     * One presence pass: poll every reachable peer in parallel and record any that
     * answer into the pool registry. Resolves with the ids recorded this pass.
     * Never rejects.
     */
    pullOnce(): Promise<{
        recorded: string[];
    }>;
    /**
     * REPLICATION-GATED: for a freshly-recorded peer, if the journal-delta deps are
     * wired AND the peer's advert shows a kind ahead of what we hold (same
     * incarnation, higher lastSeq — OR a kind we hold nothing for), request that
     * kind's delta and apply it. The send/drive path is therefore active ONLY when
     * the server passed these deps, which it does ONLY when replication is
     * explicitly enabled. Never throws (a failing fetch/apply is swallowed — the
     * presence pass must always complete).
     */
    private driveJournalDelta;
}
//# sourceMappingURL=PeerPresencePuller.d.ts.map