"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.REQUIRED_QUALIFICATION_NODE_MAJORS = exports.RUNTIME_QUALIFICATION_LANES = void 0;
exports.validateRuntimeQualification = validateRuntimeQualification;
exports.assertRuntimeQualified = assertRuntimeQualified;
exports.createRuntimeQualificationFixture = createRuntimeQualificationFixture;
exports.RUNTIME_QUALIFICATION_LANES = Object.freeze([
    "repository-source",
    "repository-dist",
    "packaged-artifact",
    "staged-installed",
    "actual-runtime"
]);
exports.REQUIRED_QUALIFICATION_NODE_MAJORS = Object.freeze([20, 22]);
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
const REVISION_PATTERN = /^[0-9a-f]{40,64}$/;
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
function validateRuntimeQualification(input) {
    const findings = [];
    const observations = [input.source, input.dist, input.packaged, input.stagedInstalled, ...input.actualRuntimes];
    const add = (code, message, observation) => {
        findings.push(Object.freeze({
            code,
            message,
            lane: observation?.lane ?? null,
            observationId: observation?.observationId ?? null
        }));
    };
    validateTopLevelContract(input, add);
    validateNamedLane(input.source, "repository-source", "source", add);
    validateNamedLane(input.dist, "repository-dist", "dist", add);
    validateNamedLane(input.packaged, "packaged-artifact", "packaged", add);
    validateNamedLane(input.stagedInstalled, "staged-installed", "stagedInstalled", add);
    for (const observation of input.actualRuntimes) {
        validateNamedLane(observation, "actual-runtime", "actualRuntimes", add);
    }
    for (const observation of observations) {
        validateObservationShape(observation, input.protocolVersion, add);
    }
    validateObservationIdentity(observations, add);
    validateVersionGroups(input, add);
    validateLaneSemantics(input, add);
    validateNodeCoverage(input, add);
    validateCandidateIdentity(input, add);
    validateBehaviorAgreement(input, add);
    const observedNodeMajors = uniqueSortedNumbers(input.actualRuntimes
        .map((observation) => parseNodeMajor(observation.nodeVersion))
        .filter((major) => major !== null));
    return Object.freeze({
        verdict: findings.length === 0 ? "qualified" : "rejected",
        findings: Object.freeze(findings),
        observedLanes: exports.RUNTIME_QUALIFICATION_LANES,
        observedNodeMajors: Object.freeze(observedNodeMajors)
    });
}
function assertRuntimeQualified(input) {
    const result = validateRuntimeQualification(input);
    if (result.verdict === "rejected") {
        const codes = result.findings.map((finding) => finding.code).join(", ");
        throw new Error(`Runtime qualification rejected: ${codes}`);
    }
}
function createRuntimeQualificationFixture() {
    const revision = "861435ec14c3dfe33e344c98181200ab91c8dfff";
    const distIdentity = `sha256:${"1".repeat(64)}`;
    const candidateBuild = `sha256:${"2".repeat(64)}`;
    const candidateContent = `sha256:${"3".repeat(64)}`;
    const behaviors = Object.freeze({
        "command-contract": "sha256:commands-valid",
        "read-surfaces": "sha256:reads-agree"
    });
    const observation = (lane, observationId, overrides = {}) => Object.freeze({
        observationId,
        lane,
        cliVersion: "0.1.23",
        protocolVersion: 2,
        nodeVersion: "22.12.0",
        buildIdentity: candidateBuild,
        contentIdentity: candidateContent,
        sourceRevision: null,
        repositoryDirtyState: "not-applicable",
        distributionFreshness: "current",
        packageIntegrity: "verified",
        warningCount: 0,
        stderr: "",
        behaviors: Object.freeze({ ...behaviors }),
        ...overrides
    });
    return Object.freeze({
        candidateVersion: "0.1.23",
        repositoryVersion: "0.0.0",
        protocolVersion: 2,
        requiredNodeMajors: exports.REQUIRED_QUALIFICATION_NODE_MAJORS,
        requiredBehaviorIds: Object.freeze(["command-contract", "read-surfaces"]),
        source: observation("repository-source", "source-contract", {
            cliVersion: "0.0.0",
            buildIdentity: `sha256:${"0".repeat(64)}`,
            contentIdentity: null,
            sourceRevision: revision,
            repositoryDirtyState: "dirty",
            distributionFreshness: "not-applicable",
            packageIntegrity: "not-applicable"
        }),
        dist: observation("repository-dist", "repository-dist-node22", {
            cliVersion: "0.0.0",
            buildIdentity: distIdentity,
            contentIdentity: distIdentity,
            sourceRevision: revision,
            repositoryDirtyState: "dirty",
            packageIntegrity: "computed-unverified"
        }),
        packaged: observation("packaged-artifact", "candidate-package", {
            sourceRevision: revision,
            repositoryDirtyState: "dirty",
            behaviors: Object.freeze({})
        }),
        stagedInstalled: observation("staged-installed", "candidate-staging-node22"),
        actualRuntimes: Object.freeze([
            observation("actual-runtime", "candidate-node20", { nodeVersion: "20.19.0" }),
            observation("actual-runtime", "candidate-node22", { nodeVersion: "22.12.0" })
        ])
    });
}
function validateTopLevelContract(input, add) {
    if (!VERSION_PATTERN.test(input.candidateVersion)) {
        add("CANDIDATE_VERSION_INVALID", "Candidate version must be an explicit semantic version.");
    }
    if (!VERSION_PATTERN.test(input.repositoryVersion)) {
        add("REPOSITORY_VERSION_INVALID", "Repository version must be an explicit semantic version.");
    }
    if (!Number.isSafeInteger(input.protocolVersion) || input.protocolVersion < 1) {
        add("PROTOCOL_VERSION_INVALID", "Protocol version must be a positive safe integer.");
    }
    const requiredNodeMajors = uniqueSortedNumbers(input.requiredNodeMajors);
    if (requiredNodeMajors.length !== exports.REQUIRED_QUALIFICATION_NODE_MAJORS.length ||
        requiredNodeMajors.some((major, index) => major !== exports.REQUIRED_QUALIFICATION_NODE_MAJORS[index]) ||
        requiredNodeMajors.length !== input.requiredNodeMajors.length) {
        add("SUPPORTED_NODE_CONTRACT_MISMATCH", "Qualification must require each supported Node major 20 and 22 exactly once.");
    }
    const normalizedBehaviorIds = input.requiredBehaviorIds.map((id) => id.trim());
    if (normalizedBehaviorIds.length === 0 ||
        normalizedBehaviorIds.some((id) => id.length === 0) ||
        new Set(normalizedBehaviorIds).size !== normalizedBehaviorIds.length) {
        add("BEHAVIOR_CONTRACT_INVALID", "Required behavior identities must be non-empty and unique.");
    }
}
function validateNamedLane(observation, expectedLane, field, add) {
    if (observation.lane !== expectedLane) {
        add("LANE_MIXED", `${field} must retain lane ${expectedLane}, observed ${observation.lane}.`, observation);
    }
}
function validateObservationShape(observation, protocolVersion, add) {
    if (observation.observationId.trim().length === 0) {
        add("OBSERVATION_ID_MISSING", "Observation identity is required.", observation);
    }
    if (!VERSION_PATTERN.test(observation.cliVersion)) {
        add("CLI_VERSION_INVALID", "CLI version must be an explicit semantic version.", observation);
    }
    if (observation.protocolVersion !== protocolVersion) {
        add("PROTOCOL_VERSION_MISMATCH", "Observation protocol version does not match the qualification contract.", observation);
    }
    if (parseNodeMajor(observation.nodeVersion) === null) {
        add("NODE_VERSION_INVALID", "Observation Node version must begin with a numeric major.", observation);
    }
    if (!SHA256_PATTERN.test(observation.buildIdentity)) {
        add("BUILD_IDENTITY_INVALID", "Observation build identity must be a SHA-256 identity.", observation);
    }
    if (observation.contentIdentity !== null && !SHA256_PATTERN.test(observation.contentIdentity)) {
        add("CONTENT_IDENTITY_INVALID", "Observation content identity must be null or a SHA-256 identity.", observation);
    }
    if (observation.warningCount !== 0 || observation.stderr.length > 0) {
        add("WARNING_EVIDENCE_PRESENT", "Qualification observations must carry zero warnings and empty stderr.", observation);
    }
    if (!Number.isSafeInteger(observation.warningCount) || observation.warningCount < 0) {
        add("WARNING_COUNT_INVALID", "Warning count must be a non-negative safe integer.", observation);
    }
}
function validateObservationIdentity(observations, add) {
    const seen = new Set();
    for (const observation of observations) {
        if (seen.has(observation.observationId)) {
            add("OBSERVATION_ID_DUPLICATE", `Observation identity ${observation.observationId} is duplicated.`, observation);
        }
        seen.add(observation.observationId);
    }
}
function validateVersionGroups(input, add) {
    for (const observation of [input.source, input.dist]) {
        if (observation.cliVersion !== input.repositoryVersion) {
            add("REPOSITORY_VERSION_MISMATCH", "Source and dist observations must report the declared repository version.", observation);
        }
    }
    for (const observation of [input.packaged, input.stagedInstalled, ...input.actualRuntimes]) {
        if (observation.cliVersion !== input.candidateVersion) {
            add("CANDIDATE_VERSION_MISMATCH", "Packaged and installed observations must report the single candidate version.", observation);
        }
    }
}
function validateLaneSemantics(input, add) {
    for (const observation of [input.source, input.dist, input.packaged]) {
        if (observation.sourceRevision === null || !REVISION_PATTERN.test(observation.sourceRevision)) {
            add("SOURCE_REVISION_MISSING", "Repository and packaged observations require a source revision.", observation);
        }
        if (!isKnownRepositoryState(observation.repositoryDirtyState)) {
            add("REPOSITORY_STATE_UNKNOWN", "Repository and packaged observations require a known dirty state.", observation);
        }
    }
    if (input.source.distributionFreshness !== "not-applicable" || input.source.packageIntegrity !== "not-applicable") {
        add("SOURCE_LANE_MIXED", "Repository-source evidence cannot claim dist freshness or package integrity.", input.source);
    }
    if (input.source.contentIdentity !== null) {
        add("SOURCE_CONTENT_IDENTITY_MIXED", "Repository-source content identity must remain separate from distribution content.", input.source);
    }
    if (input.dist.distributionFreshness !== "current") {
        add("DISTRIBUTION_STALE", "Repository-dist evidence must be current.", input.dist);
    }
    if (input.dist.packageIntegrity !== "computed-unverified") {
        add("DIST_INTEGRITY_INVALID", "Repository-dist evidence must retain its computed-unverified package boundary.", input.dist);
    }
    if (input.dist.sourceRevision !== input.source.sourceRevision || input.dist.repositoryDirtyState !== input.source.repositoryDirtyState) {
        add("SOURCE_DIST_PROVENANCE_MISMATCH", "Source and dist observations must agree on source revision and dirty state.", input.dist);
    }
    if (input.packaged.sourceRevision !== input.source.sourceRevision || input.packaged.repositoryDirtyState !== input.source.repositoryDirtyState) {
        add("PACKAGE_SOURCE_PROVENANCE_MISMATCH", "Packaged evidence must retain the source revision and dirty state used to build it.", input.packaged);
    }
    for (const observation of [input.packaged, input.stagedInstalled, ...input.actualRuntimes]) {
        if (observation.packageIntegrity !== "verified") {
            add("PACKAGE_INTEGRITY_UNVERIFIED", "Candidate package and installed observations require verified integrity.", observation);
        }
        if (observation.distributionFreshness !== "current") {
            add("CANDIDATE_DISTRIBUTION_NOT_CURRENT", "Candidate package and installed observations require current distribution evidence.", observation);
        }
    }
    for (const observation of [input.stagedInstalled, ...input.actualRuntimes]) {
        if (observation.sourceRevision !== null || observation.repositoryDirtyState !== "not-applicable") {
            add("INSTALLED_REPOSITORY_EVIDENCE_MIXED", "Installed observations cannot impersonate live repository state.", observation);
        }
    }
}
function validateNodeCoverage(input, add) {
    const seen = new Set();
    for (const observation of input.actualRuntimes) {
        const major = parseNodeMajor(observation.nodeVersion);
        if (major === null)
            continue;
        if (!input.requiredNodeMajors.includes(major)) {
            add("UNSUPPORTED_NODE_RUNTIME", `Actual runtime uses undeclared Node major ${major}.`, observation);
        }
        if (seen.has(major)) {
            add("NODE_RUNTIME_DUPLICATE", `Node major ${major} has more than one actual-runtime observation.`, observation);
        }
        seen.add(major);
    }
    for (const major of input.requiredNodeMajors) {
        if (!seen.has(major)) {
            add("SUPPORTED_NODE_RUNTIME_MISSING", `Node major ${major} has no actual-runtime observation.`);
        }
    }
}
function validateCandidateIdentity(input, add) {
    const installed = [input.packaged, input.stagedInstalled, ...input.actualRuntimes];
    const expectedBuild = input.packaged.buildIdentity;
    const expectedContent = input.packaged.contentIdentity;
    if (expectedContent === null) {
        add("PACKAGE_CONTENT_IDENTITY_MISSING", "Packaged artifact requires a candidate content identity.", input.packaged);
    }
    for (const observation of installed.slice(1)) {
        if (observation.buildIdentity !== expectedBuild) {
            add("CANDIDATE_BUILD_IDENTITY_MISMATCH", "Installed build identity differs from the packaged artifact.", observation);
        }
        if (observation.contentIdentity !== expectedContent) {
            add("CANDIDATE_CONTENT_IDENTITY_MISMATCH", "Installed content identity differs from the packaged artifact.", observation);
        }
    }
}
function validateBehaviorAgreement(input, add) {
    const executable = [input.source, input.dist, input.stagedInstalled, ...input.actualRuntimes];
    for (const behaviorId of input.requiredBehaviorIds) {
        const expected = input.source.behaviors[behaviorId];
        if (typeof expected !== "string" || expected.trim().length === 0) {
            add("BEHAVIOR_EVIDENCE_MISSING", `Source behavior ${behaviorId} is missing.`, input.source);
            continue;
        }
        for (const observation of executable.slice(1)) {
            const observed = observation.behaviors[behaviorId];
            if (typeof observed !== "string" || observed.trim().length === 0) {
                add("BEHAVIOR_EVIDENCE_MISSING", `Behavior ${behaviorId} is missing.`, observation);
            }
            else if (observed !== expected) {
                add("BEHAVIOR_DISAGREEMENT", `Behavior ${behaviorId} disagrees with repository-source evidence.`, observation);
            }
        }
    }
}
function parseNodeMajor(version) {
    const match = /^(?:v)?(\d+)(?:\.|$)/.exec(version);
    if (match === null)
        return null;
    const major = Number(match[1]);
    return Number.isSafeInteger(major) && major > 0 ? major : null;
}
function isKnownRepositoryState(state) {
    return state === "clean" || state === "dirty";
}
function uniqueSortedNumbers(values) {
    return [...new Set(values.filter((value) => Number.isSafeInteger(value) && value > 0))]
        .sort((left, right) => left - right);
}
