/**
 * Devbox image protocol-version handshake (Decision #14).
 *
 * Closes T13 — cross-namespace protocol skew between CLI and overridden
 * devbox image (e.g. atlas devbox v0.5 + CLI 0.2 with new host-cp wire
 * format). Without this handshake, a CLI/devbox version mismatch
 * silently corrupts; with it, the CLI refuses to start the world and
 * exits with a named code + actionable remedy.
 *
 * Contract: every devbox image declares `LABEL olam.protocol.versions=
 * "<comma-separated-major-list>"` (see docs/architecture/devbox-contract.md
 * §1). The CLI carries a compiled-in `OLAM_PROTOCOL_VERSIONS_SUPPORTED`
 * list and demands non-empty intersection with the image's declaration
 * post-pull, before any container is started.
 */
import { spawnSync } from 'node:child_process';
/**
 * Protocol versions this CLI release knows how to drive.
 *
 * **Bump procedure**:
 *   1. When the CLI ↔ host-cp wire format gains a backward-incompatible
 *      change, append the new version to this list.
 *   2. Update `packages/adapters/src/docker/devbox.Dockerfile` to set
 *      `LABEL olam.protocol.versions="<old>,<new>"` during the
 *      deprecation window (typically 2 minor releases).
 *   3. Drop the old version from this list once the deprecation window
 *      closes.
 *
 * **Compatibility rule**: a CLI MAY support newer versions than its
 * devbox declares — pick the highest mutually-supported version at
 * handshake time. Empty intersection → refuse with EXIT_PROTOCOL_MISMATCH.
 */
export const OLAM_PROTOCOL_VERSIONS_SUPPORTED = [1];
/**
 * Parse the value of `olam.protocol.versions` LABEL into a sorted,
 * deduplicated list of major-version integers. Tolerates whitespace,
 * empty strings, and malformed entries (silently skipped — the CLI's
 * empty-intersection check handles "image declares nothing valid").
 */
export function parseProtocolVersionsLabel(labelValue) {
    if (!labelValue)
        return [];
    const parts = labelValue.split(',').map((s) => s.trim()).filter(Boolean);
    const versions = new Set();
    for (const part of parts) {
        const n = Number.parseInt(part, 10);
        if (Number.isFinite(n) && n > 0 && String(n) === part) {
            versions.add(n);
        }
    }
    return Array.from(versions).sort((a, b) => a - b);
}
/**
 * Check whether the image's declared protocol versions overlap with the
 * CLI's supported list. Returns the overlap (empty when incompatible)
 * plus a named-remedy string for stderr emission on `compatible: false`.
 *
 * The remedy names BOTH version sets so the operator can see exactly
 * why the handshake failed and which CLI version pins they should try.
 */
export function checkProtocolOverlap(imageVersions, cliVersions = OLAM_PROTOCOL_VERSIONS_SUPPORTED) {
    const cliSet = new Set(cliVersions);
    const overlap = imageVersions.filter((v) => cliSet.has(v));
    if (imageVersions.length === 0) {
        return {
            overlap: [],
            imageVersions: [],
            cliVersions,
            compatible: false,
            remedy: 'Devbox image is missing the `olam.protocol.versions` LABEL. ' +
                `This CLI requires versions [${cliVersions.join(', ')}]. ` +
                'See docs/architecture/devbox-contract.md §1 for the contract; ' +
                'rebuild the image with `LABEL olam.protocol.versions="1"`.',
        };
    }
    if (overlap.length === 0) {
        const imgLow = Math.min(...imageVersions);
        const imgHigh = Math.max(...imageVersions);
        const cliLow = Math.min(...cliVersions);
        const cliHigh = Math.max(...cliVersions);
        return {
            overlap: [],
            imageVersions: [...imageVersions],
            cliVersions,
            compatible: false,
            remedy: `Devbox image protocol versions [${imageVersions.join(', ')}] ` +
                `don't overlap CLI's [${cliVersions.join(', ')}]. ` +
                (imgHigh < cliLow
                    ? `The image is older than this CLI supports — rebuild against the contract version ${cliLow}+ at docs/architecture/devbox-contract.md.`
                    : imgLow > cliHigh
                        ? `The image is newer than this CLI supports — pin a compatible CLI: \`npm install -g @pleri/olam-cli@<version-with-protocol-${imgLow}>\`.`
                        : `Pin a compatible CLI version that overlaps with the image's range.`),
        };
    }
    return {
        overlap,
        imageVersions: [...imageVersions],
        cliVersions,
        compatible: true,
        remedy: '',
    };
}
const realDockerInspect = (imageRef) => {
    const result = spawnSync('docker', ['inspect', imageRef, '--format', '{{ index .Config.Labels "olam.protocol.versions" }}'], { encoding: 'utf8', timeout: 10_000 });
    return {
        exitCode: result.status ?? -1,
        stdout: result.stdout ?? '',
        stderr: result.stderr ?? '',
    };
};
export function inspectImageProtocolVersions(imageRef, dockerInspect = realDockerInspect) {
    const { exitCode, stdout, stderr } = dockerInspect(imageRef);
    if (exitCode !== 0) {
        return {
            imageRef,
            versions: [],
            inspectFailed: true,
            inspectError: stderr.trim() || `docker inspect exited ${exitCode}`,
        };
    }
    // `docker inspect --format` for a missing LABEL returns the literal
    // string "<no value>" or just an empty newline depending on engine
    // version. Parse defensively.
    const raw = stdout.trim();
    const value = raw === '<no value>' ? '' : raw;
    return {
        imageRef,
        versions: parseProtocolVersionsLabel(value),
        inspectFailed: false,
        inspectError: undefined,
    };
}
//# sourceMappingURL=protocol-version.js.map