function stableStringify(value) {
    if (value === null || typeof value !== 'object')
        return JSON.stringify(value);
    if (Array.isArray(value))
        return '[' + value.map(stableStringify).join(',') + ']';
    const entries = Object.keys(value).sort();
    return '{' + entries.map((k) => JSON.stringify(k) + ':' + stableStringify(value[k])).join(',') + '}';
}
function runtimeLabel(runtime) {
    if (!runtime)
        return 'missing';
    const bits = [`${runtime.executor}:${runtime.model}`, `fp=${runtime.fingerprint}`];
    if (runtime.binary?.version)
        bits.push(`binary=${runtime.binary.version}`);
    if (runtime.sdk?.version)
        bits.push(`sdk=${runtime.sdk.version}`);
    return bits.join(' ');
}
function push(warnings, code, zh, en) {
    warnings.push({ code, severity: 'warning', zh, en });
}
function sorted(values) {
    return [...(values ?? [])].sort();
}
function hasJudge(meta) {
    return Boolean(meta.judgeModels && meta.judgeModels.length > 0 && !meta.noJudge);
}
function judgeKeys(meta) {
    return (meta.judgeModels ?? []).map((j) => `${j.executor}:${j.model}`);
}
function effectiveJudgeRepeat(meta) {
    return meta.judgeRepeat ?? 1;
}
function packageReasons(pkg, label) {
    if (!pkg)
        return [`${label} missing`];
    const reasons = [];
    if (pkg.error)
        reasons.push(`${label} probe failed`);
    if (!pkg.version)
        reasons.push(`${label} version missing`);
    return reasons;
}
function runtimeUnverifiableReasons(runtime) {
    const reasons = [];
    if (runtime.kind === 'api')
        return reasons;
    if (runtime.kind === 'agent-sdk') {
        reasons.push(...packageReasons(runtime.sdk, 'sdk'));
    }
    const binary = runtime.binary;
    if (!binary) {
        reasons.push('binary missing');
        return reasons;
    }
    if (binary.error)
        reasons.push('binary probe failed');
    if (binary.source === 'path') {
        if (!binary.path)
            reasons.push('binary path missing');
        if (!binary.version)
            reasons.push('binary version missing');
    }
    else if (binary.source === 'bundled') {
        if (!binary.version && !binary.package?.version)
            reasons.push('bundled binary version missing');
        if (binary.package)
            reasons.push(...packageReasons(binary.package, 'bundled package').filter((r) => r !== 'bundled package missing'));
    }
    else if (binary.source === 'unknown') {
        reasons.push('binary source unknown');
    }
    if (runtime.kind === 'unknown')
        reasons.push('runtime kind unknown');
    return [...new Set(reasons)];
}
function pushRuntimeUnverifiable(warnings, code, zhSubject, enSubject, runtime) {
    if (!runtime)
        return;
    const reasons = runtimeUnverifiableReasons(runtime);
    if (reasons.length === 0)
        return;
    push(warnings, code, `${zhSubject} runtime 指纹不可完全审计: ${runtimeLabel(runtime)} (${reasons.join('; ')})。`, `${enSubject} runtime fingerprint is not fully verifiable: ${runtimeLabel(runtime)} (${reasons.join('; ')}).`);
}
function runtimeMapKeys(meta) {
    return sorted(meta.executorRuntimes ? Object.keys(meta.executorRuntimes) : undefined);
}
function reportExecutorRuntimeWarnings(report, warnings) {
    const runtimes = report.meta.executorRuntimes;
    if (runtimes && Object.keys(runtimes).length > 0) {
        const missing = report.meta.variants.filter((variant) => !runtimes[variant]);
        if (missing.length > 0) {
            push(warnings, 'executor_runtime_missing', `报告缺少部分 variant 的 executor runtime 指纹: ${missing.join(', ')}。`, `Report is missing executor runtime fingerprints for variants: ${missing.join(', ')}.`);
        }
        for (const [variant, runtime] of Object.entries(runtimes)) {
            pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', `variant ${variant} executor`, `Variant ${variant} executor`, runtime);
        }
        return;
    }
    if (!report.meta.executorRuntime) {
        push(warnings, 'executor_runtime_missing', '报告缺少 executor runtime 指纹；无法审计 binary / SDK 版本，跨报告严格比较需谨慎。', 'Report is missing executor runtime fingerprint; binary / SDK versions cannot be audited for strict cross-report comparison.');
        return;
    }
    if (report.meta.variants.length > 1) {
        push(warnings, 'executor_runtimes_missing', '报告缺少 per-variant executor runtime 指纹；多 variant run 无法审计每个分组实际使用的 binary / SDK 版本。', 'Report is missing per-variant executor runtime fingerprints; multi-variant runs cannot audit each group’s actual binary / SDK version.');
    }
    pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', 'executor', 'Executor', report.meta.executorRuntime);
}
function countSampleHashMismatches(a, b) {
    const ah = a.meta.sampleHashes;
    const bh = b.meta.sampleHashes;
    if (!ah || !bh)
        return null;
    let mismatched = 0;
    let missing = 0;
    let common = 0;
    const ids = new Set([...Object.keys(ah), ...Object.keys(bh)]);
    for (const id of ids) {
        if (ah[id] == null || bh[id] == null) {
            missing++;
            continue;
        }
        common++;
        if (ah[id] !== bh[id])
            mismatched++;
    }
    return { mismatched, missing, common };
}
export function reportComparabilityWarnings(report) {
    const warnings = [];
    reportExecutorRuntimeWarnings(report, warnings);
    if (hasJudge(report.meta) && !report.meta.judgePromptHash) {
        push(warnings, 'judge_prompt_hash_missing', '报告缺少评委提示词指纹；无法确认 LLM 评分语义是否一致。', 'Report is missing judge prompt fingerprint; LLM scoring semantics cannot be verified.');
    }
    // Judge runtime audit: every entry in meta.judgeModels carries its own runtime fingerprint.
    // Skip when noJudge=true (judge didn't run, runtime intentionally absent).
    if (hasJudge(report.meta)) {
        const entries = report.meta.judgeModels ?? [];
        const missing = entries.filter((e) => !e.runtime).map((e) => `${e.executor}:${e.model}`);
        if (missing.length > 0) {
            push(warnings, 'judge_runtime_missing', entries.length === 1
                ? '报告缺少评委 runtime 指纹；无法审计评委 executor 的 binary / SDK 版本。'
                : `多评委 ensemble 缺少 runtime 指纹: ${missing.join(', ')}。`, entries.length === 1
                ? 'Report is missing judge runtime fingerprint; judge executor binary / SDK versions cannot be audited.'
                : `Multi-judge ensemble is missing runtime fingerprints: ${missing.join(', ')}.`);
        }
        for (const e of entries) {
            if (!e.runtime)
                continue;
            const label = entries.length === 1 ? '评委' : `评委 ${e.executor}:${e.model}`;
            const enLabel = entries.length === 1 ? 'Judge' : `Judge ${e.executor}:${e.model}`;
            pushRuntimeUnverifiable(warnings, 'judge_runtime_unverifiable', label, enLabel, e.runtime);
        }
    }
    if (!report.meta.sampleHashes) {
        push(warnings, 'sample_hashes_missing', '报告缺少用例指纹；无法确认不同 run 是否测的是同一组用例。', 'Report is missing sample fingerprints; runs cannot be verified to use the same test cases.');
    }
    if (!report.meta.skillIsolation) {
        push(warnings, 'skill_isolation_missing', '报告缺少 skill isolation 快照；baseline 是否被 skill 污染不可审计。', 'Report is missing skill-isolation snapshot; baseline contamination cannot be audited.');
    }
    return warnings;
}
export function crossReportComparabilityWarnings(before, after) {
    const warnings = [];
    const b = before.meta;
    const a = after.meta;
    if (b.model !== a.model) {
        push(warnings, 'model_mismatch', `任务执行模型不同: ${b.model} → ${a.model}。`, `Task execution model changed: ${b.model} → ${a.model}.`);
    }
    if (b.executor !== a.executor) {
        push(warnings, 'executor_mismatch', `executor 不同: ${b.executor} → ${a.executor}。`, `Executor changed: ${b.executor} → ${a.executor}.`);
    }
    const bExecutorRuntimeKeys = runtimeMapKeys(b);
    const aExecutorRuntimeKeys = runtimeMapKeys(a);
    const hasExecutorRuntimeMap = bExecutorRuntimeKeys.length > 0 || aExecutorRuntimeKeys.length > 0;
    if (hasExecutorRuntimeMap) {
        const keys = sorted([...new Set([...b.variants, ...a.variants, ...bExecutorRuntimeKeys, ...aExecutorRuntimeKeys])]);
        const missing = keys.filter((key) => !b.executorRuntimes?.[key] || !a.executorRuntimes?.[key]);
        if (missing.length > 0) {
            push(warnings, 'executor_runtime_missing', `至少一份报告缺少 per-variant executor runtime 指纹: ${missing.join(', ')}。`, `At least one report is missing per-variant executor runtime fingerprints: ${missing.join(', ')}.`);
        }
        for (const key of keys) {
            const beforeRuntime = b.executorRuntimes?.[key];
            const afterRuntime = a.executorRuntimes?.[key];
            if (beforeRuntime?.fingerprint && afterRuntime?.fingerprint && beforeRuntime.fingerprint !== afterRuntime.fingerprint) {
                push(warnings, 'executor_runtime_mismatch', `variant ${key} executor runtime 指纹不同: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}。`, `Variant ${key} executor runtime fingerprint changed: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}.`);
            }
            pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', `before variant ${key} executor`, `Before variant ${key} executor`, beforeRuntime);
            pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', `after variant ${key} executor`, `After variant ${key} executor`, afterRuntime);
        }
    }
    else if (b.executorRuntime?.fingerprint && a.executorRuntime?.fingerprint) {
        if (b.executorRuntime.fingerprint !== a.executorRuntime.fingerprint) {
            push(warnings, 'executor_runtime_mismatch', `executor runtime 指纹不同: ${runtimeLabel(b.executorRuntime)} → ${runtimeLabel(a.executorRuntime)}。`, `Executor runtime fingerprint changed: ${runtimeLabel(b.executorRuntime)} → ${runtimeLabel(a.executorRuntime)}.`);
        }
    }
    else {
        push(warnings, 'executor_runtime_missing', '至少一份报告缺少 executor runtime 指纹；无法确认 binary / SDK 版本一致。', 'At least one report is missing executor runtime fingerprint; binary / SDK version parity cannot be verified.');
    }
    if (!hasExecutorRuntimeMap) {
        pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', 'before executor', 'Before executor', b.executorRuntime);
        pushRuntimeUnverifiable(warnings, 'executor_runtime_unverifiable', 'after executor', 'After executor', a.executorRuntime);
    }
    // Judge comparability — only meaningful when at least one report actually ran a judge.
    // - 都 noJudge: skip 整段(judge 没跑,任何比较都误导;两份 noJudge 报告 judge 视角下完全可比)
    // - 一边 noJudge 一边 judged: 给 judge_presence_mismatch 一条明确警告,跳过 runtime/model 比较
    //   (这两份报告本身就不在同一个测量框架,详细 runtime / model diff 是噪声)
    // - 两边都 judged: 走 model / repeat / runtime full audit
    const bJudged = hasJudge(b);
    const aJudged = hasJudge(a);
    if (!bJudged && !aJudged) {
        // 都没跑评委,judge audit 完全跳过;evaluationFramework / skillIsolation 等非 judge 项继续往下走。
    }
    else if (bJudged !== aJudged) {
        push(warnings, 'judge_presence_mismatch', `评委开关不同: ${bJudged ? 'judged' : 'no-judge'} → ${aJudged ? 'judged' : 'no-judge'}。两次跑测的测量框架不一致(LLM 维度缺失),不可在 judge 维度做严格对比。`, `Judge presence differs: ${bJudged ? 'judged' : 'no-judge'} → ${aJudged ? 'judged' : 'no-judge'}. The runs are in different measurement frames (LLM dimension absent on one side); judge-axis comparison is not meaningful.`);
    }
    else {
        // 两边都 judged — single judge 是 ensemble 的 1-entry degenerate case,同一段代码处理。
        const bKeys = sorted(judgeKeys(b));
        const aKeys = sorted(judgeKeys(a));
        if (stableStringify(bKeys) !== stableStringify(aKeys)) {
            const isSingle = bKeys.length <= 1 && aKeys.length <= 1;
            push(warnings, isSingle ? 'judge_model_mismatch' : 'judge_models_mismatch', isSingle
                ? `评委模型不同: ${bKeys[0] ?? 'none'} → ${aKeys[0] ?? 'none'}。`
                : `多评委 ensemble 配置不同: ${bKeys.join(', ') || 'none'} → ${aKeys.join(', ') || 'none'}。`, isSingle
                ? `Judge model changed: ${bKeys[0] ?? 'none'} → ${aKeys[0] ?? 'none'}.`
                : `Multi-judge ensemble changed: ${bKeys.join(', ') || 'none'} → ${aKeys.join(', ') || 'none'}.`);
        }
        if (effectiveJudgeRepeat(b) !== effectiveJudgeRepeat(a)) {
            push(warnings, 'judge_repeat_mismatch', `每条用例评委评价次数不同: ${effectiveJudgeRepeat(b)} → ${effectiveJudgeRepeat(a)}。`, `Judge repeat count changed: ${effectiveJudgeRepeat(b)} → ${effectiveJudgeRepeat(a)}.`);
        }
        // Per-judge runtime fingerprint comparison. Iterate the union of judges across both reports.
        const judgeUnion = sorted([...new Set([...bKeys, ...aKeys])]);
        if (judgeUnion.length > 0) {
            const bRuntimeByKey = new Map((b.judgeModels ?? []).map((e) => [`${e.executor}:${e.model}`, e.runtime]));
            const aRuntimeByKey = new Map((a.judgeModels ?? []).map((e) => [`${e.executor}:${e.model}`, e.runtime]));
            const missing = judgeUnion.filter((key) => !bRuntimeByKey.get(key) || !aRuntimeByKey.get(key));
            if (missing.length > 0) {
                const isSingle = judgeUnion.length === 1;
                push(warnings, 'judge_runtime_missing', isSingle
                    ? '至少一份报告缺少评委 runtime 指纹；无法确认评委 binary / SDK 版本一致。'
                    : `至少一份报告缺少多评委 runtime 指纹: ${missing.join(', ')}。`, isSingle
                    ? 'At least one report is missing judge runtime fingerprint; judge binary / SDK version parity cannot be verified.'
                    : `At least one report is missing multi-judge runtime fingerprints: ${missing.join(', ')}.`);
            }
            for (const key of judgeUnion) {
                const beforeRuntime = bRuntimeByKey.get(key);
                const afterRuntime = aRuntimeByKey.get(key);
                if (beforeRuntime?.fingerprint && afterRuntime?.fingerprint && beforeRuntime.fingerprint !== afterRuntime.fingerprint) {
                    push(warnings, 'judge_runtime_mismatch', judgeUnion.length === 1
                        ? `评委 runtime 指纹不同: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}。`
                        : `评委 ${key} runtime 指纹不同: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}。`, judgeUnion.length === 1
                        ? `Judge runtime fingerprint changed: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}.`
                        : `Judge ${key} runtime fingerprint changed: ${runtimeLabel(beforeRuntime)} → ${runtimeLabel(afterRuntime)}.`);
                }
                const labelPrefix = judgeUnion.length === 1 ? '评委' : `评委 ${key}`;
                const enLabelPrefix = judgeUnion.length === 1 ? 'judge' : `judge ${key}`;
                pushRuntimeUnverifiable(warnings, 'judge_runtime_unverifiable', `before ${labelPrefix}`, `Before ${enLabelPrefix}`, beforeRuntime);
                pushRuntimeUnverifiable(warnings, 'judge_runtime_unverifiable', `after ${labelPrefix}`, `After ${enLabelPrefix}`, afterRuntime);
            }
        }
    }
    // Prompt hash 只在两边都 judged 时比较;一边 noJudge 一边 judged 由上面 judge_presence_mismatch 已覆盖,
    // 两边 noJudge 时双方都没 hash 也不报错。
    if (bJudged && aJudged) {
        if (!b.judgePromptHash || !a.judgePromptHash) {
            push(warnings, 'judge_prompt_hash_missing', `至少一份报告缺少评委提示词指纹: ${b.judgePromptHash ?? 'missing'} → ${a.judgePromptHash ?? 'missing'}。`, `At least one report is missing judge prompt hash: ${b.judgePromptHash ?? 'missing'} → ${a.judgePromptHash ?? 'missing'}.`);
        }
        else if (b.judgePromptHash !== a.judgePromptHash) {
            push(warnings, 'judge_prompt_hash_mismatch', `评委提示词指纹不同: ${b.judgePromptHash ?? 'missing'} → ${a.judgePromptHash ?? 'missing'}。`, `Judge prompt hash changed: ${b.judgePromptHash ?? 'missing'} → ${a.judgePromptHash ?? 'missing'}.`);
        }
    }
    if ((b.evaluationFramework || a.evaluationFramework) && b.evaluationFramework !== a.evaluationFramework) {
        push(warnings, 'evaluation_framework_mismatch', `统计框架不同: ${b.evaluationFramework ?? 'legacy'} → ${a.evaluationFramework ?? 'legacy'}。`, `Evaluation framework changed: ${b.evaluationFramework ?? 'legacy'} → ${a.evaluationFramework ?? 'legacy'}.`);
    }
    if (stableStringify(b.skillIsolation ?? null) !== stableStringify(a.skillIsolation ?? null)) {
        push(warnings, 'skill_isolation_mismatch', 'skill isolation 快照不同；baseline 污染边界不一致，严格比较需谨慎。', 'Skill-isolation snapshots differ; baseline contamination boundaries are not equivalent.');
    }
    const sampleHashDiff = countSampleHashMismatches(before, after);
    if (!sampleHashDiff) {
        push(warnings, 'sample_hashes_missing', '至少一份报告缺少用例指纹；无法确认两次 run 使用同一组用例。', 'At least one report is missing sample fingerprints; test-case parity cannot be verified.');
    }
    else if (sampleHashDiff.mismatched > 0 || sampleHashDiff.missing > 0) {
        push(warnings, 'sample_hashes_mismatch', `用例指纹不一致: ${sampleHashDiff.mismatched} 条内容变化，${sampleHashDiff.missing} 条只出现在其中一份报告。`, `Sample fingerprints differ: ${sampleHashDiff.mismatched} content mismatches, ${sampleHashDiff.missing} samples only appear in one report.`);
    }
    return warnings;
}
export function formatComparabilityWarnings(warnings, lang = 'zh') {
    if (warnings.length === 0)
        return '';
    const header = lang === 'zh'
        ? '可比性提示: 以下差异会影响 strict comparison / construct validity'
        : 'Comparability warnings: the following differences affect strict comparison / construct validity';
    return [header, ...warnings.map((warning) => `  - ${lang === 'zh' ? warning.zh : warning.en}`)].join('\n');
}
//# sourceMappingURL=comparability.js.map