/**
 * Run coordinator for multi-orchestrator job routing.
 *
 * The receiving orchestrator becomes the run coordinator: it claims jobs
 * it can dispatch locally and reroutes others to peers based on agent
 * inventory. Per locked decisions:
 * - "One orchestrator coordinates per webhook run"
 * - "Claim-based splitting"
 * - "Coordinator handles ALL check run reporting"
 * - "Peers report step-by-step progress back to coordinator"
 * - "Cancel mode: graceful -- finish current step, cancel remaining"
 */
import type { JobReroute, JobProgress, PeerScalerEvent, PeerToPeerMessage, ResourceRequest } from '@kici-dev/engine';
import type { PeerRegistry } from './peer-registry.js';
import type { PeerClient } from './peer-client.js';
import type { Dispatcher } from '../agent/dispatcher.js';
import type { ExecutionTracker } from '../reporting/execution-tracker.js';
import type { CheckRunReporter } from '../reporting/check-run-reporter.js';
export interface RunContext {
    runId: string;
    deliveryId: string;
    routingKey: string;
    event: string;
    action: string | null;
    provider: string;
    payload: Record<string, unknown>;
    repoIdentifier: string;
    sha: string;
    ref: string;
    workflowName: string;
    installationId?: number;
    requestId?: string;
    traceId?: string;
    /** Pre-resolved clone token for workers without provider credentials. */
    cloneToken?: string;
}
export interface JobToRoute {
    jobName: string;
    runsOnLabels: string[][];
    jobConfig: Record<string, unknown>;
    repoUrl: string;
    ref: string;
    sha: string;
    sourceTarUrl?: string;
    sourceTarHash?: string;
    depsUrl?: string;
    depsHash?: string;
    /** Labels that the dispatched agent must NOT have. */
    excludeLabels?: string[];
    /**
     * Per-job resource request and limit (K8s-style). Forwarded to
     * `Dispatcher.dispatch()` so the scaler's per-scaler / per-orchestrator /
     * per-machine cap accounting sees the typed value rather than relying on
     * the `jobConfig.resources` JSON blob (which the dispatcher does not parse).
     */
    resources?: ResourceRequest;
}
export interface RouteResult {
    localJobs: Array<{
        jobName: string;
        jobId: string;
    }>;
    /**
     * Rerouted jobs include the pre-allocated jobId so the caller can
     * register the execution_runs / execution_jobs rows under the same id
     * the worker will report progress against.
     */
    reroutedJobs: Array<{
        jobName: string;
        peerId: string;
        jobId: string;
    }>;
    failedJobs: Array<{
        jobName: string;
        reason: string;
    }>;
}
export interface RunCoordinatorDeps {
    instanceId: string;
    peerRegistry: PeerRegistry;
    dispatcher: Dispatcher;
    executionTracker?: ExecutionTracker;
    checkRunReporter?: CheckRunReporter;
    getPeerClient: (instanceId: string) => PeerClient | undefined;
    /** Fallback: send job.reroute via server-side (incoming) peer connection and wait for ACK. */
    sendAndWaitAckViaHandler?: (targetInstanceId: string, msg: JobReroute, timeoutMs: number) => Promise<boolean>;
    /** Fallback: send a message via server-side (incoming) peer connection (fire-and-forget). */
    sendToPeerViaHandler?: (targetInstanceId: string, msg: PeerToPeerMessage) => boolean;
    ackTimeoutMs?: number;
    /** Stale peer timeout in ms. Default: 60000 (60s). */
    staleTimeoutMs?: number;
}
export declare class RunCoordinator {
    private readonly instanceId;
    private readonly peerRegistry;
    private readonly dispatcher;
    private readonly executionTracker?;
    private readonly checkRunReporter?;
    private readonly getPeerClient;
    private readonly sendAndWaitAckViaHandler?;
    private readonly sendToPeerViaHandler?;
    private readonly ackTimeoutMs;
    /**
     * Tracks which jobs have been rerouted to which peers, keyed by runId.
     * Used for cancel propagation and progress tracking.
     */
    private readonly reroutedJobs;
    /**
     * NAK tracking per peer: count of consecutive NAKs and backoff-until timestamp.
     * Peers that repeatedly NAK are deprioritized via exponential backoff.
     */
    private readonly nakTracker;
    /** Stale eviction timer handle. */
    private staleEvictionTimer;
    constructor(deps: RunCoordinatorDeps);
    /**
     * Route jobs for a webhook run. Claims jobs that local agents can handle,
     * reroutes the rest to peers with matching capacity.
     *
     * Parallel fan-out: jobs targeting different peers are rerouted concurrently.
     */
    routeJobs(runContext: RunContext, jobs: JobToRoute[]): Promise<RouteResult>;
    /**
     * Handle a job reroute request received from a peer (this instance is the worker).
     * Checks loop prevention, then attempts to dispatch locally.
     */
    handleIncomingReroute(msg: JobReroute): Promise<{
        accepted: boolean;
        reason?: string;
    }>;
    /**
     * Handle peer job/step progress updates from a worker (or coord) peer.
     *
     * The discriminator `msg.kind` decides which downstream tracker call to
     * make: 'job' updates feed `onJobStatus` (the path that drives run-level
     * state transitions and the rerouted-job cleanup); 'step' updates feed
     * `onStepStatus` (which only persists `execution_steps` rows). Without
     * this split, every job-level event was silently funnelled into
     * `onStepStatus` and the run stayed in `running` forever.
     */
    onPeerJobProgress(msg: JobProgress): void;
    /**
     * Handle a scaler provisioning event forwarded by a worker peer.
     *
     * Workers have no database, so they relay scaler events for jobs the
     * worker is provisioning to the coordinator that owns the run. The
     * coordinator's ExecutionTracker persists the event (provisioning log
     * line + dispatch-queue last-error column) just as it would for a
     * locally-emitted scaler event.
     */
    onPeerScalerEvent(msg: PeerScalerEvent): void;
    /**
     * Handle peer job completion. Updates ExecutionTracker.
     * The execution tracker's onJobStatus fires the needs-aware scheduler hook,
     * which evaluates downstream jobs and dispatches newly-ready ones via onJobReadyCallback.
     * The data parameter carries droppedJobs for drift reporting.
     */
    onPeerJobComplete(runId: string, jobId: string, status: string, timestamp: number, data?: Record<string, unknown>): void;
    /**
     * Cancel all rerouted jobs for a run. Sends peer.job.cancel to all
     * peers that have jobs for this run.
     *
     * Per locked decision: "graceful -- finish current step, cancel remaining."
     */
    cancelRun(runId: string, reason: string): void;
    /**
     * Check if the peer registry has any connected peers.
     */
    hasConnectedPeers(): boolean;
    /**
     * Start the stale eviction timer. Calls peerRegistry.evictStalePeers()
     * every staleTimeoutMs/2 to detect and remove stale peers.
     */
    startStaleEvictionTimer(staleTimeoutMs: number): void;
    /**
     * Stop the stale eviction timer.
     */
    stopStaleEvictionTimer(): void;
    /**
     * Get the NAK count for a peer (used in tests).
     */
    getNakCount(peerId: string): number;
    /**
     * Get the backoff-until timestamp for a peer (used in tests).
     */
    getBackoffUntil(peerId: string): number;
    /**
     * Attempt to reroute a job to a peer with matching capacity.
     * Tries peers in order of available capacity (most capacity first).
     * Returns on first successful ACK.
     */
    private rerouteJob;
    /**
     * Sort peers by available capacity for the given label sets.
     * Peers with more available capacity come first.
     */
    private sortPeersByCapacity;
    /**
     * Compute total available capacity across matching agents on a peer.
     *
     * Applies the same `mandatoryLabels` gate the local label matcher
     * applies — a gated agent only contributes capacity for label sets that
     * include every gate label. Without it, the coordinator would route a
     * job to a peer based on a gated agent's labels, and the peer would
     * then refuse the dispatch in `AgentRegistry.findAvailable` because the
     * agent's gate is not satisfied.
     */
    private computeAvailableCapacity;
    /**
     * Track a rerouted job for cancel propagation.
     */
    private trackReroutedJob;
}
//# sourceMappingURL=coordinator.d.ts.map