/**
 * Shared rubric template resolution for all evaluation modes.
 *
 * Resolves templated LLM-rubric assertions (those with `template` + `criteria`
 * fields) into fully assembled Promptfoo assertions with rubric text and
 * dimension metadata.
 *
 * Used by both literacy and agent-harness compilers. Extracted from
 * literacy/assertions.ts to fix the compilation bug where agent-harness
 * tasks with templated rubrics produced empty rubric text (DOC-2029).
 *
 * @see docs/design-docs/mode-agnostic-scoring.md
 * @see docs/design-docs/two-stage-grader-symbol-preflight.md — W0198 Phase 6
 * @see config/rubrics.ts — template definitions
 */
// ---------------------------------------------------------------------------
// Template resolution
// ---------------------------------------------------------------------------
/**
 * Resolve a templated LLM-rubric assertion into a fully assembled
 * Promptfoo assertion with rubric text and dimension metadata.
 *
 * A "templated" assertion has `template` (referencing a key in rubrics.ts)
 * and `criteria` (task-specific bullet points). The template provides the
 * scoring header, scale, and dimension metadata. The criteria are appended
 * to create the final rubric prompt.
 *
 * When `canonicalReference` is supplied, it is emitted on the assertion's
 * `rubricPrompt` field — Promptfoo's per-assertion grader-prompt override —
 * wrapped in a ground-truth framing so the grader treats the supplied
 * content as authoritative. The rubric `value` itself is unchanged. Without
 * it, the grader falls back on training priors and may hallucinate against
 * neighboring API surfaces.
 *
 * Returns null (with a warning) if the template can't be resolved.
 */
export function resolveTemplatedAssertion(assertion, rubricConfig, graderProvider, warnings, canonicalReference, preflightContext) {
    if (!rubricConfig) {
        warnings.push(`No rubric config — template "${assertion.template}" cannot be resolved`);
        return null;
    }
    const template = rubricConfig.templates[assertion.template];
    if (!template) {
        warnings.push(`Unknown rubric template: "${assertion.template}"`);
        return null;
    }
    const scaleText = template.scale.map((s) => `- ${s}`).join("\n");
    const criteriaText = assertion.criteria
        .map((c) => renderCriterion(c, assertion.template))
        .join("\n");
    // W0198 Phase 6 — when the deterministic preflight lane is wired and this
    // rubric scores `code-correctness`, prefix a system instruction so the
    // grader does not re-judge symbol existence. The lane separation is the
    // whole reason W0198 exists; if both lanes weigh existence, the rubric's
    // hallucinations sneak back into a dimension we want deterministic.
    const preflightSection = preflightContext && template.dimension === "code-correctness"
        ? buildPreflightSection(preflightContext)
        : "";
    // Plan 03-02 — when the template carries a per-dimension legal-mode
    // list, announce it to the grader before the structured-shape footer
    // (Plan 03-01). Empty list => no announcement (preserves prior wording
    // for templates that haven't yet been wired to a taxonomy family).
    const failureModesLine = template.failureModes && template.failureModes.length > 0
        ? `\nThe "failureMode" must be one of: ${template.failureModes.join(", ")}.\n\n`
        : "";
    const rubricValue = preflightSection +
        `${template.header}\n${scaleText}\n\n` +
        `${template.criteria_label ?? "Check for:"}\n${criteriaText}\n\n` +
        failureModesLine +
        `${rubricConfig.footer}`;
    const rubricPrompt = canonicalReference
        ? buildRubricPromptWithReference(rubricValue, canonicalReference)
        : undefined;
    return {
        type: "llm-rubric",
        value: rubricValue,
        ...(rubricPrompt ? { rubricPrompt } : {}),
        ...(graderProvider ? { provider: graderProvider } : {}),
        ...(template.dimension
            ? { metadata: { dimension: template.dimension, maxScore: 100 } }
            : {}),
    };
}
/**
 * Render a single rubric criterion to its bullet line.
 *
 * Criteria come in two shapes: the canonical `CriterionRef` object
 * (`{ id, text }`, used by Content Lake / API tasks) and a legacy bare
 * string (still used by many repo `defineTask` task files). Both render to
 * their text. Interpolating a `CriterionRef` object directly (`- ${c}`)
 * silently renders `- [object Object]`, stripping the grader's acceptance
 * criteria and degrading grade quality — that is the bug this guards. A
 * criterion that yields no renderable text (e.g. an object missing `text`)
 * fails loud at compile time rather than reaching the grader.
 */
function renderCriterion(criterion, template) {
    const text = typeof criterion === "string" ? criterion : criterion?.text;
    if (typeof text !== "string" || text.trim() === "") {
        throw new Error(`Rubric template "${template}" has a criterion with no renderable text ` +
            `(received: ${JSON.stringify(criterion)?.slice(0, 160)}). Each criterion ` +
            `must be a non-empty string or a { id, text } object; an object without ` +
            `text renders "[object Object]" in the grader prompt.`);
    }
    return `- ${text}`;
}
/**
 * Build the W0198 Phase 6 preflight preface for a `code-correctness`
 * rubric. Returned with a trailing newline so it composes cleanly with
 * the existing rubric body.
 */
export function buildPreflightSection(context) {
    const packageList = context.packages.length > 0
        ? context.packages.join(", ")
        : "(no packages in manifest)";
    return [
        "DETERMINISTIC PREFLIGHT — A separate lane checks whether each named",
        "binding the candidate imports (e.g. `import { foo } from 'pkg'`) actually",
        "exports from its source package, against the actual `.d.ts` of the",
        "installed version. Default and namespace imports (`import x from 'pkg'`,",
        "`import * as x from 'pkg'`) are NOT preflight-checked and fall through to",
        "you. Treat any existence verdict the preflight returns as ground truth —",
        "do NOT include symbol-existence concerns in your `code-correctness`",
        "judgment. Confine your code-correctness score to idiomatic usage, code",
        "organization, type safety, and completeness against the task.",
        "",
        `In-scope packages (preflight rules on named bindings imported from`,
        `these): ${packageList}.`,
        "",
        "",
    ].join("\n");
}
/**
 * Build the full rubric-prompt string sent to the grader when canonical
 * reference content is available. The framing explicitly tells the grader
 * to treat the reference as authoritative ground truth so it cannot fall
 * back on training priors when checking symbol existence or API shape.
 *
 * Promptfoo interpolates `{{output}}` and `{{rubric}}` at grade time —
 * `{{output}}` is the candidate response; `{{rubric}}` is the assertion's
 * `value` field (the rubric text built above).
 */
function buildRubricPromptWithReference(rubric, reference) {
    return [
        "You are grading a candidate response against a rubric.",
        "",
        "AUTHORITATIVE REFERENCE — treat this as ground truth. If a symbol, function,",
        "API, or pattern appears here, it exists; do not contradict it from prior",
        "knowledge. If something is absent here, do not assume it exists.",
        "",
        "--- BEGIN REFERENCE ---",
        reference,
        "--- END REFERENCE ---",
        "",
        "RUBRIC:",
        rubric,
        "",
        "CANDIDATE RESPONSE:",
        "{{output}}",
    ].join("\n");
}
