/**
 * pipeline/enrichment-preconditions.ts
 *
 * Classifies the inputs the post-scoring enrichment steps (gap-analysis,
 * compute-attribution) depend on, so a missing `grader-judgments.json` can be
 * told apart as either a legitimate skip (no graded eval ran this pipeline) or
 * a degraded outcome (a full eval scored tests but no judgments persisted).
 *
 * The degraded case is the failure these steps must stop swallowing:
 * `calculate-scores` wrote `score-summary.json` with per-area `testCount > 0`
 * but never wrote `grader-judgments.json`, so the enrichment steps self-skip
 * and the report ships with no test details while still showing a score.
 * Distinguishing the two is what lets the steps fail loud instead of returning
 * a benign `skipped`.
 */
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
/**
 * Classify the enrichment inputs for a run by inspecting
 * `results/latest/grader-judgments.json` and `score-summary.json`.
 *
 * Pure read-only filesystem inspection — never throws on malformed input; a
 * file that does not parse to the expected shape is treated as absent so that
 * "no usable judgments" and "no usable summary" both collapse to a single
 * branch.
 */
export function classifyEnrichmentInputs(rootDir) {
    const judgmentCount = countGraderJudgments(rootDir);
    if (judgmentCount > 0) {
        return { kind: "ready", judgmentCount };
    }
    const scoredTestCount = scoredTestCountFromSummary(rootDir);
    if (scoredTestCount > 0) {
        return { kind: "judgments-missing-after-eval", scoredTestCount };
    }
    return { kind: "no-full-eval" };
}
/**
 * Build the fail-loud error message for the degraded
 * `judgments-missing-after-eval` case. Shared by the enrichment steps so the
 * pipeline-result and job-document surfaces carry one consistent wording.
 */
export function degradedEnrichmentError(step, scoredTestCount) {
    return (`${step}: grader-judgments.json missing after a full eval — ` +
        `${scoredTestCount} test(s) scored but 0 grader judgments persisted. ` +
        `The report is marked degraded rather than published as healthy.`);
}
/**
 * Count the judgments in `grader-judgments.json`. Returns 0 when the file is
 * absent, unreadable, not valid JSON, or not an array — every "no usable
 * judgments" shape collapses to 0 so callers branch on a single number. An
 * empty array is therefore indistinguishable from a missing file by design
 * (both are "no judgments persisted").
 */
function countGraderJudgments(rootDir) {
    const path = resolve(rootDir, "results", "latest", "grader-judgments.json");
    if (!existsSync(path))
        return 0;
    try {
        const parsed = JSON.parse(readFileSync(path, "utf-8"));
        return Array.isArray(parsed) ? parsed.length : 0;
    }
    catch {
        return 0;
    }
}
/**
 * Sum the per-area `testCount` from `score-summary.json` — the signal that a
 * full eval scored tests this run. Returns 0 when the summary is absent,
 * unreadable, or carries no scored tests.
 */
function scoredTestCountFromSummary(rootDir) {
    const path = resolve(rootDir, "results", "latest", "score-summary.json");
    if (!existsSync(path))
        return 0;
    try {
        const parsed = JSON.parse(readFileSync(path, "utf-8"));
        const scores = Array.isArray(parsed.scores) ? parsed.scores : [];
        return scores.reduce((sum, s) => sum + (typeof s.testCount === "number" ? s.testCount : 0), 0);
    }
    catch {
        return 0;
    }
}
