/**
 * 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.
 */
/**
 * 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 const SESSION_STATUS_ADVERT_FIELDS = [
    '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 function narrowSessionStatusToPeerCapacity(raw, unwrappedJournalAdvert) {
    if (!raw || typeof raw !== 'object')
        return null;
    const cap = raw;
    return {
        selfReportedLastSeen: cap.selfReportedLastSeen,
        loadAvg: cap.loadAvg,
        journalAdvert: unwrappedJournalAdvert,
        ...(cap.commitmentsAdvert !== undefined ? { commitmentsAdvert: cap.commitmentsAdvert } : {}),
        ...(cap.preferencesAdvert !== undefined ? { preferencesAdvert: cap.preferencesAdvert } : {}),
        ...(cap.quotaState !== undefined ? { quotaState: cap.quotaState } : {}),
        ...(cap.guardPosture !== undefined ? { guardPosture: cap.guardPosture } : {}),
        ...(cap.servesChannels !== undefined ? { servesChannels: cap.servesChannels } : {}),
        // THE FIX (4th instance of the narrowing-return-forgets-a-field class): the
        // peer's receive advert must cross the wire or replication never starts.
        ...(cap.seamlessnessFlags !== undefined ? { seamlessnessFlags: cap.seamlessnessFlags } : {}),
    };
}
export class PeerPresencePuller {
    d;
    constructor(d) {
        this.d = d;
    }
    /**
     * 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.
     */
    async pullOnce() {
        const peers = this.d.listPeers().filter((m) => m.machineId !== this.d.selfMachineId && !!m.url);
        const results = await Promise.all(peers.map(async (m) => {
            let cap = null;
            try {
                cap = await this.d.fetchPeerCapacity(m.machineId, m.url);
            }
            catch {
                cap = null; // unreachable / rejected → not recorded; ages out of online
            }
            if (!cap)
                return null;
            const seen = cap.selfReportedLastSeen ?? (this.d.now?.() ?? new Date()).toISOString();
            this.d.recordHeartbeat({ machineId: m.machineId, selfReportedLastSeen: seen, loadAvg: cap.loadAvg, ...(cap.quotaState !== undefined ? { quotaState: cap.quotaState } : {}), ...(cap.guardPosture !== undefined ? { guardPosture: cap.guardPosture } : {}), ...(cap.seamlessnessFlags !== undefined ? { seamlessnessFlags: cap.seamlessnessFlags } : {}), ...(cap.servesChannels !== undefined ? { servesChannels: cap.servesChannels } : {}) });
            // REPLICATION-GATED journal-delta drive — only when the server wired the
            // delta deps (i.e. replication.enabled === true). Otherwise a complete
            // no-op (engine/transport stay dark). Never throws into the puller.
            await this.driveJournalDelta(m.machineId, m.url, cap.journalAdvert);
            if (cap.commitmentsAdvert && this.d.driveCommitmentsSync) {
                try {
                    await this.d.driveCommitmentsSync(m.machineId, m.url, cap.commitmentsAdvert);
                }
                catch { /* @silent-fallback-ok: the puller's contract is NEVER to throw — a failed commitments pull retries on the next presence pass (COMMITMENTS-COHERENCE-SPEC §3.2) */
                }
            }
            if (cap.preferencesAdvert && this.d.drivePreferencesSync) {
                try {
                    await this.d.drivePreferencesSync(m.machineId, m.url, cap.preferencesAdvert);
                }
                catch { /* @silent-fallback-ok: the puller's contract is NEVER to throw — a failed preferences pull retries on the next presence pass (MULTI-MACHINE-SEAMLESSNESS-SPEC §WS2.1) */
                }
            }
            try {
                this.d.onPeerRecorded?.(m.machineId);
            }
            catch { /* @silent-fallback-ok: the puller's contract is NEVER to throw — a pending-pull re-arm failure must not break the presence pass (WORKING-SET-HANDOFF-SPEC §3.4) */
            }
            return m.machineId;
        }));
        const recorded = results.filter((r) => r !== null);
        if (recorded.length && this.d.log)
            this.d.log(`recorded ${recorded.length} peer(s) online over HTTP: ${recorded.join(', ')}`);
        return { recorded };
    }
    /**
     * 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).
     */
    async driveJournalDelta(machineId, url, peerAdvert) {
        // Gate: all three deps must be present (server wires them only when
        // replication.enabled === true). Any absent → no-op (dark).
        const { requestJournalDelta, applyDelta, localAdvertFor } = this.d;
        if (!requestJournalDelta || !applyDelta || !localAdvertFor)
            return;
        if (!peerAdvert || typeof peerAdvert !== 'object')
            return;
        let local = {};
        try {
            local = localAdvertFor(machineId) || {};
        }
        catch { /* @silent-fallback-ok: the puller's contract is NEVER to throw (presence must always complete); an empty local view safely treats any peer seq as ahead and the applier de-dupes on apply */
            local = {};
        }
        for (const [kind, peer] of Object.entries(peerAdvert)) {
            try {
                if (!peer || typeof peer.lastSeq !== 'number' || !Number.isFinite(peer.lastSeq))
                    continue;
                const held = local[kind];
                // We hold nothing for this kind → request from 0. We hold the SAME
                // incarnation but the peer is ahead → request from our lastSeq. A
                // DIFFERENT incarnation is still requested from our lastSeq; the applier
                // performs the incarnation-fencing/quarantine on apply (its job, not
                // ours) — so we still pull and let it reconcile.
                const fromSeq = held && typeof held.lastSeq === 'number' ? held.lastSeq : 0;
                const peerAhead = !held || peer.incarnation !== held.incarnation || peer.lastSeq > fromSeq;
                if (!peerAhead)
                    continue;
                let delta = null;
                try {
                    delta = await requestJournalDelta(machineId, url, kind, fromSeq);
                }
                catch { /* @silent-fallback-ok: an unreachable/rejected delta fetch is a transient, self-healing condition — the next presence pass re-requests; surfacing it here would break the puller's never-throw contract */
                    delta = null;
                }
                if (!delta)
                    continue;
                try {
                    applyDelta(machineId, [delta]);
                }
                catch { /* @silent-fallback-ok: apply() is itself fully tolerant + observable via its own counters; this guard only preserves the puller's never-throw contract */ }
            }
            catch { /* @silent-fallback-ok: per-kind drive is best-effort; one bad kind must never poison the rest of the presence pass (the puller's never-throw contract) */ }
        }
    }
}
//# sourceMappingURL=PeerPresencePuller.js.map