/**
 * promptfoo-grader-output.ts — Zod schema for the structured grader output
 * (GRAD-02) emitted by the promptfoo grader process and consumed by the
 * eval pipeline.
 *
 * The schema asserts `satisfies z.ZodType<GraderJudgment>` against the
 * canonical domain type in `packages/core/src/types/grader-judgment.ts`
 * (D0045 / W0187) — drift between schema and type is a build error.
 * The domain type was authored independently in Plan 01-01; this file
 * authors ONLY the schema and never derives the domain type from the
 * schema itself (no schema-derived self-reference allowed by D0045).
 *
 * `graderJudgmentsVersion` is co-located with the schema (VER-01 D-02 —
 * source-of-truth file owns its version constant). Bumped by hand when
 * the grader rubric, prompt template, or judgment shape changes.
 *
 * Phase 3 will replace the inline `JSON.parse` at
 * `pipeline/calculate-scores.ts:380-392` (Pitfall #4) so all grader
 * output flows through this schema.
 *
 * @see docs/decisions/D0045-type-architecture-and-contract-enforcement.md
 * @see docs/decisions/D0049-shared-confidence-contract.md
 * @see docs/decisions/D0052-judgment-ref-granularity.md
 * @see docs/design-docs/actionability-ladder/03-structured-grader-judgments.md
 */
import { z } from "zod";
import { brandedString, ConfidenceSchema } from "../../_vendor/ailf-core/schemas/index.js";
/**
 * VER-01 D-02 — co-located version constant. Bumped by hand when the
 * grader rubric, prompt template, or judgment shape changes in a way
 * that should invalidate cached Diagnoses.
 *
 * Phase 3 GRAD-05 bumped this from `"0.1.0"` to `"1.0.0"` (semver
 * major) — the additive GRAD-02 surface is now required + the schema
 * is `.strict()`. AILF has no installed external base; the legacy
 * parser at `./legacy/promptfoo-grader-output-legacy.ts` is the named
 * consumer for already-stored historical reports.
 */
export const graderJudgmentsVersion = "1.0.0";
const DocCitationRoleSchema = z.enum([
    "supports",
    "contradicts",
    "missing",
    "irrelevant",
]);
const DocCitationSchema = z.object({
    documentId: z.string().min(1),
    slug: z.string().optional(),
    role: DocCitationRoleSchema,
    hallucinated: z.boolean().optional(),
});
const CriterionSubJudgmentSchema = z.object({
    criterionId: z.string().min(1),
    met: z.boolean(),
    evidence: z.string().max(280),
    confidence: ConfidenceSchema,
});
/**
 * Canonical schema for {@link GraderJudgment}. Required fields mirror
 * the existing pipeline core (Doc 03 §"existing, unchanged"):
 * `taskId`, `modelId`, `dimension`, `reason`, `score`. Phase 3 GRAD-05
 * has tightened the additive surface to required and added `.strict()`
 * — the schema rejects unknown fields (defense-in-depth against future
 * prompt-injection attempts that try to smuggle keys through the
 * grader emission).
 *
 * Branded `JudgmentId` is represented at runtime by a non-empty string;
 * the schema routes the brand through `brandedString<"JudgmentId">()`
 * — the project's single audited cast site for branded-string
 * schemas (project typescript rule: no `as` on `unknown`).
 */
export const GraderJudgmentSchema = z
    .object({
    // ── Existing pipeline core (required — Doc 03 §"existing, unchanged") ─
    taskId: z.string().min(1),
    modelId: z.string().min(1),
    dimension: z.string().min(1),
    reason: z.string(),
    score: z.number(),
    outputFailure: z.boolean().optional(),
    // ── GRAD-02 additive — required from Phase 3 GRAD-05 ───────────────
    judgmentId: brandedString(),
    subJudgments: z.array(CriterionSubJudgmentSchema),
    docCitations: z.array(DocCitationSchema),
    failureMode: z.string(),
    confidence: ConfidenceSchema,
    hallucinationCheckedAgainst: z.array(z.string()),
    metadata: z.object({
        graderModel: z.string().min(1),
        graderJudgmentsVersion: z.string().min(1),
    }),
})
    .strict();
/**
 * Wire-format schema — what the grader LLM is asked to emit.
 *
 * This is the subset of {@link GraderJudgmentSchema} that contains only
 * fields the LLM can correctly produce. The pipeline parses untrusted
 * grader output against this shape, then synthesizes the four
 * pipeline-owned fields (`judgmentId`, `metadata.graderModel`,
 * `metadata.graderJudgmentsVersion`, `hallucinationCheckedAgainst`) plus
 * the three result-context fields (`taskId`, `modelId`, `dimension`) to
 * build the full {@link GraderJudgment} storage shape.
 *
 * `.strict()` is retained — the LLM is told exactly which keys to emit,
 * so any extras are either prompt-injection attempts or noise we want
 * to surface as parse failures (which then drop to the
 * `synthesizeUnparsedJudgment` fallback). The bar to hit that fallback
 * is much lower than before W0273 — previously a missing pipeline-owned
 * field tripped it on every emission.
 *
 * Asserts `satisfies z.ZodType<GraderEmittedJudgment>` against the
 * independently-authored core type (D0045). The trust-boundary CI gate
 * (`pnpm check-trust-boundary-satisfies`) covers this file.
 *
 * @see docs/audits/2026-05-22-empty-gap-analysis-regression.md
 */
export const GraderEmittedJudgmentSchema = z
    .object({
    score: z.number(),
    reason: z.string(),
    failureMode: z.string(),
    subJudgments: z.array(CriterionSubJudgmentSchema),
    docCitations: z.array(DocCitationSchema),
    confidence: ConfidenceSchema,
    outputFailure: z.boolean().optional(),
})
    .strict();
