{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/core/evaluation/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAK7E,MAAM,WAAW,0BAA0B;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,0BAA0B,CAoCzF;AAED,wBAAgB,YAAY,CAC3B,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,kBAAkB,EAAE,GAC7B,0BAA0B,CAoB5B","sourcesContent":["import type { EvaluationScenario, EvaluationScenarioPack } from \"./types.js\";\n\nconst SUPPORTED_SCHEMA_VERSION = 1;\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\nexport interface EvaluationValidationResult {\n\tvalid: boolean;\n\terrors: string[];\n\twarnings: string[];\n}\n\nexport function validateScenario(scenario: EvaluationScenario): EvaluationValidationResult {\n\tconst errors: string[] = [];\n\tconst warnings: string[] = [];\n\tif (!scenario || typeof scenario !== \"object\")\n\t\treturn { valid: false, errors: [\"scenario must be an object\"], warnings };\n\tif (!ID_PATTERN.test(scenario.scenarioId))\n\t\terrors.push(\"scenarioId must contain only letters, numbers, dot, underscore, or hyphen\");\n\tif (!Number.isInteger(scenario.scenarioVersion) || scenario.scenarioVersion < 1)\n\t\terrors.push(\"scenarioVersion must be a positive integer\");\n\tif (!scenario.title.trim()) errors.push(\"title is required\");\n\tif (!scenario.task?.prompt?.trim()) errors.push(\"task.prompt is required\");\n\tif (!Number.isInteger(scenario.repetitions) || scenario.repetitions < 1 || scenario.repetitions > 100)\n\t\terrors.push(\"repetitions must be between 1 and 100\");\n\tif (!Number.isInteger(scenario.timeoutMs) || scenario.timeoutMs < 1) errors.push(\"timeoutMs must be positive\");\n\tif (scenario.fixture.kind === \"local\" && !scenario.fixture.root) errors.push(\"local fixtures require fixture.root\");\n\tif (scenario.fixture.kind === \"inline\" && !scenario.fixture.files)\n\t\terrors.push(\"inline fixtures require fixture.files\");\n\tif (scenario.fixture.allowExternalSymlinks !== undefined) errors.push(\"external symlinks are not permitted\");\n\tconst assertionIds = new Set<string>();\n\tfor (const assertion of scenario.assertions) {\n\t\tif (assertionIds.has(assertion.assertionId)) errors.push(`duplicate assertion id: ${assertion.assertionId}`);\n\t\tassertionIds.add(assertion.assertionId);\n\t\tif (!ID_PATTERN.test(assertion.assertionId)) errors.push(`invalid assertion id: ${assertion.assertionId}`);\n\t\tif (assertion.kind === \"command\" && !assertion.command)\n\t\t\terrors.push(`command assertion ${assertion.assertionId} requires command`);\n\t}\n\tconst metricIds = new Set<string>();\n\tfor (const metric of scenario.metrics) {\n\t\tif (metricIds.has(metric.metricId)) errors.push(`duplicate metric id: ${metric.metricId}`);\n\t\tmetricIds.add(metric.metricId);\n\t\tif (!ID_PATTERN.test(metric.metricId)) errors.push(`invalid metric id: ${metric.metricId}`);\n\t}\n\tif (!scenario.candidatePolicy.allowedModes.length) errors.push(\"candidatePolicy.allowedModes must not be empty\");\n\tif (scenario.candidatePolicy.allowNetwork && scenario.provenance.classification !== \"public\")\n\t\twarnings.push(\"network-enabled scenarios should be explicitly classified as public\");\n\treturn { valid: errors.length === 0, errors, warnings };\n}\n\nexport function validatePack(\n\tpack: EvaluationScenarioPack,\n\tscenarios: EvaluationScenario[],\n): EvaluationValidationResult {\n\tconst errors: string[] = [];\n\tconst warnings: string[] = [];\n\tif (!pack || typeof pack !== \"object\") return { valid: false, errors: [\"pack must be an object\"], warnings: [] };\n\tif (pack.schemaVersion !== SUPPORTED_SCHEMA_VERSION)\n\t\terrors.push(`unsupported pack schema version: ${pack.schemaVersion}`);\n\tif (!ID_PATTERN.test(pack.packId)) errors.push(`invalid pack id: ${pack.packId}`);\n\tconst seen = new Set<string>();\n\tfor (const scenarioId of pack.scenarios) {\n\t\tif (seen.has(scenarioId)) errors.push(`duplicate scenario id in pack: ${scenarioId}`);\n\t\tseen.add(scenarioId);\n\t\tconst scenario = scenarios.find((candidate) => candidate.scenarioId === scenarioId);\n\t\tif (!scenario) errors.push(`missing scenario: ${scenarioId}`);\n\t\telse {\n\t\t\tconst result = validateScenario(scenario);\n\t\t\terrors.push(...result.errors.map((error) => `${scenarioId}: ${error}`));\n\t\t\twarnings.push(...result.warnings.map((warning) => `${scenarioId}: ${warning}`));\n\t\t}\n\t}\n\treturn { valid: errors.length === 0, errors, warnings };\n}\n"]}