/**
 * port-forward.ts — kubectl port-forward lifecycle manager for olam-cli.
 *
 * Phase 1b C1 of olam-host-suite-phase-1b-k3s-beta-flavour (plan
 * ~/.claude/plans/olam-host-suite-phase-1b-k3s-beta-flavour.md).
 *
 * Phase 2 Phase B B2 (olam-host-suite-phase-2-peripheral-services-on-k3s):
 *   Extended with registry-driven multi-peripheral support:
 *     spawnAllPeripheralPortForwards() — iterate PERIPHERALS; spawn each in parallel
 *     stopAllPeripheralPortForwards()  — SIGTERM each peripheral's port-forward
 *     getPeripheralLivenessSnapshot()  — TCP probe all 4 peripherals; return {name, alive}[]
 *   Each peripheral uses its own independent flock + PID file named by peripheral.name.
 *   host-cp port-forward retained as a named special-case; Phase 1b callers unchanged.
 *
 * Decisions consumed:
 *   D17 — port-forward spawn via flock advisory lock (race-safe)
 *   D23 — PID file at ~/.olam/state/port-forward.pid (mode 0600)
 *   D24 — probePortForwardLiveness uses TCP probe, NOT kill(pid, 0)
 *   D26 — per-peripheral PID/lock at ~/.olam/state/port-forward-<name>.{pid,lock}
 *
 * ## Flock strategy (why O_CREAT|O_EXCL, not POSIX flock(2))
 *
 * Node.js has no built-in `flock(2)` syscall binding. Instead we use
 * `fs.openSync(lockPath, 'wx')` which maps to `open(O_CREAT|O_EXCL|O_WRONLY)`.
 * On POSIX this is atomic: exactly ONE caller will succeed; concurrent callers
 * receive EEXIST. This is semantically equivalent to a non-blocking `flock`
 * for our scenario (guard the read-check-spawn sequence against concurrent
 * `olam upgrade` / `olam status` invocations). POSIX flock would additionally
 * survive fd inheritance across forks — we don't need that here because the
 * lock scope is only the spawn decision, not the port-forward subprocess lifetime.
 * Lock release: `unlinkSync(lockPath)` in a finally block.
 *
 * ## PID file lifecycle
 *   1. Acquire lock (O_CREAT|O_EXCL on lock file).
 *   2. Read PID file (if present) → TCP-probe liveness of existing forward.
 *   3. If live → release lock; return (nothing to do).
 *   4. Write PID file to a tmp path, set mode 0600, rename atomically.
 *   5. Spawn kubectl port-forward.
 *   6. Release lock.
 *   7. Port-forward runs detached (stderr suppressed); PID file removed on exit.
 *
 * ## TCP probe (D24)
 *
 * `probePortForwardLiveness` opens a TCP connection to 127.0.0.1:19000.
 * `kill(pid, 0)` would only check process existence — it cannot distinguish
 * "port-forward process alive but tunnel broken" from "tunnel working". TCP
 * is the ground truth.
 */
import { spawn } from 'node:child_process';
import * as path from 'node:path';
import * as os from 'node:os';
import { type Peripheral } from './peripheral-registry.js';
export declare const PORT_FORWARD_PORT = 19000;
export declare const PORT_FORWARD_LOCK_PATH: string;
export declare const PORT_FORWARD_PID_PATH: string;
export interface PortForwardDeps {
    /** inject for tests — overrides the state dir used for lock + PID files */
    readonly stateDir?: string;
    /** inject for tests — overrides the spawn factory */
    readonly spawnImpl?: typeof spawn;
    /** inject for tests — overrides TCP probe */
    readonly tcpProbeImpl?: (host: string, port: number, timeoutMs: number) => Promise<boolean>;
    /** inject for tests — overrides Date.now() for timestamp assertions */
    readonly now?: () => number;
}
/**
 * Probe liveness of the port-forward tunnel via a TCP connection attempt.
 *
 * Returns true only when a TCP connection to 127.0.0.1:PORT_FORWARD_PORT
 * succeeds. PID-alive check (kill(pid, 0)) is intentionally NOT used —
 * the process could be alive but the tunnel broken (per D24).
 *
 * Injectable: `deps.tcpProbeImpl` replaces the real TCP socket for tests.
 */
export declare function probePortForwardLiveness(deps?: Pick<PortForwardDeps, 'tcpProbeImpl'>): Promise<boolean>;
export type SpawnPortForwardResult = {
    spawned: true;
    pid: number;
} | {
    spawned: false;
    reason: 'live' | 'lock-held';
};
/**
 * Spawn a `kubectl port-forward` subprocess, guarded by an advisory lock
 * so concurrent callers produce exactly ONE running port-forward process.
 *
 * Protocol:
 *   1. Acquire advisory lock (throws on concurrent access → returns 'lock-held').
 *   2. TCP-probe liveness of any existing port-forward.
 *   3. If live → release lock; return { spawned: false, reason: 'live' }.
 *   4. Spawn kubectl port-forward (detached, stdio ignored).
 *   5. Write PID file atomically (mode 0600).
 *   6. Release lock.
 *   7. Unref child so the CLI process can exit without waiting for it.
 *   8. Register 'exit' listener on child to remove the PID file on teardown.
 *
 * @param context - kubectl context name (--context flag)
 * @param namespace - target namespace
 * @param target - service or pod target (e.g. 'service/olam-host-cp')
 * @param localPort - local port to bind (default: PORT_FORWARD_PORT)
 * @param remotePort - remote port on the target (default: PORT_FORWARD_PORT)
 * @param deps - injectable dependencies for testing
 */
export declare function spawnPortForward(context: string, namespace: string, target: string, localPort?: number, remotePort?: number, deps?: PortForwardDeps): Promise<SpawnPortForwardResult>;
/**
 * Kill a running port-forward identified by the PID file and remove the file.
 * No-op when no PID file exists. Returns true when a process was signalled.
 */
export declare function stopPortForward(deps?: PortForwardDeps): boolean;
/**
 * Options for peripheral port-forward operations (injectable for tests).
 * Extends the same deps pattern as the host-cp single port-forward.
 */
export interface PeripheralPortForwardDeps {
    /** Override state directory (default: OLAM_STATE_DIR). Used in tests to isolate state. */
    readonly stateDir?: string;
    /** Override spawn factory (default: child_process.spawn). */
    readonly spawnImpl?: typeof spawn;
    /** Override TCP probe (default: real TCP socket). */
    readonly tcpProbeImpl?: (host: string, port: number, timeoutMs: number) => Promise<boolean>;
}
/**
 * Spawn a kubectl port-forward for a single peripheral, guarded by an
 * independent advisory lock per peripheral. Mirrors the Phase 1b host-cp
 * pattern (D17/D23/D24) generalised to any Peripheral entry.
 *
 * Returns true when a new process was spawned; false when the tunnel was
 * already live or the lock was held by a concurrent caller.
 *
 * @internal — called by spawnAllPeripheralPortForwards; exported for test injection.
 */
export declare function spawnPeripheralPortForward(peripheral: Peripheral, context: string, namespace: string, deps?: PeripheralPortForwardDeps): Promise<boolean>;
/**
 * Spawn kubectl port-forward processes for all 4 peripherals in parallel.
 *
 * Each peripheral uses its own independent flock + PID file
 * (`~/.olam/state/port-forward-<name>.{lock,pid}`), so locking one
 * peripheral does NOT block another.
 *
 * @param context - kubectl context name (--context flag); defaults to 'olam'
 * @param namespace - target namespace; defaults to 'olam'
 * @param deps - injectable dependencies for testing
 */
export declare function spawnAllPeripheralPortForwards(context?: string, namespace?: string, deps?: PeripheralPortForwardDeps): Promise<void>;
/**
 * Send SIGTERM to all running peripheral port-forward processes and
 * remove their PID and lock files.
 *
 * No-op for any peripheral whose PID file is absent (process already stopped).
 *
 * @param deps - injectable dependencies for testing (stateDir override)
 */
export declare function stopAllPeripheralPortForwards(deps?: Pick<PeripheralPortForwardDeps, 'stateDir'>): void;
export interface PeripheralLivenessEntry {
    name: string;
    alive: boolean;
}
/**
 * TCP-probe each peripheral and return a snapshot of their liveness.
 *
 * Uses the same realTcpProbe utility as probePortForwardLiveness (D24).
 * Probes run in parallel for speed.
 *
 * @param deps - injectable dependencies for testing
 */
export declare function getPeripheralLivenessSnapshot(deps?: Pick<PeripheralPortForwardDeps, 'tcpProbeImpl'>): Promise<PeripheralLivenessEntry[]>;
export { os, path };
//# sourceMappingURL=port-forward.d.ts.map