"""Strict parser for the joint output contract (spec ยง5.1). Never raises: any malformed input yields `ParsedOutput(format_ok=False, ...)` with `parse_errors` populated. This is required by the Phase 3 gate (fuzz-tested on >=100 malformed inputs; must never crash) since parse failure feeds the R5 reward guard at training/eval time, not a Python exception. """ from __future__ import annotations import re from src.contracts.schema import ParsedOutput, TraceStep _ANALYSIS_RE = re.compile(r"(.*?)", re.DOTALL) _LABEL_RE = re.compile(r"", re.DOTALL) _CWE_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) _STATEMENTS_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) _TRACE_BLOCK_RE = re.compile(r"(.*?)", re.DOTALL) _STEP_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) _EXPLANATION_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) _STEP_LINE_RE = re.compile( r"^\s*line\s+(\d+)\s*:?\s*(.*?)\s*(?:\[\s*(.*?)\s*\])?\s*$", re.IGNORECASE ) _VALID_LABELS = {"VULNERABLE", "SAFE"} _CWE_VALUE_RE = re.compile(r"^(CWE-\d+|NONE)$", re.IGNORECASE) _RANGE_RE = re.compile(r"^\s*(\d+)\s*(?:-\s*(\d+))?\s*$") def parse_statement_lines(text: str) -> tuple[set[int], list[str]]: """Parses 'comma-separated line ranges, e.g. 12-14, 27' -> a set of line numbers.""" lines: set[int] = set() errors: list[str] = [] text = (text or "").strip() if not text: return lines, errors for chunk in text.split(","): chunk = chunk.strip() if not chunk: continue m = _RANGE_RE.match(chunk) if not m: errors.append(f"unparseable statement range token: {chunk!r}") continue start = int(m.group(1)) end = int(m.group(2)) if m.group(2) else start if end < start: errors.append(f"inverted range: {chunk!r}") continue if end - start > 10_000: errors.append(f"implausibly large range (rejected): {chunk!r}") continue lines.update(range(start, end + 1)) return lines, errors def _parse_trace(trace_block: str) -> tuple[list[TraceStep], list[str]]: steps: list[TraceStep] = [] errors: list[str] = [] for step_text in _STEP_RE.findall(trace_block): m = _STEP_LINE_RE.match(step_text) if not m: errors.append(f"unparseable trace step: {step_text!r}") continue line_no, code, tag = m.group(1), m.group(2) or "", m.group(3) or "" try: steps.append(TraceStep(line=int(line_no), code=code, tag=tag)) except (TypeError, ValueError): errors.append(f"non-integer line in trace step: {step_text!r}") return steps, errors def parse_output(raw: str, contract_version: int = 1) -> ParsedOutput: """Parses one model completion against the joint output contract. Returns format_ok=False (never raises) on any structural violation: missing block, missing/invalid