{"record":{"id":"ea96b72ab33c216c","repo":"HKUDS/Vibe-Trading","slug":"audit-rows-require-criterion-id-and-result","errorCode":null,"errorMessage":"audit rows require criterion_id and result","messagePattern":"audit rows require criterion_id and result","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/tools/goal_tool.py","lineNumber":51,"sourceCode":"\n\ndef _coerce_audit_rows(value: Any) -> list[AuditRow]:\n    \"\"\"Coerce model/API-style audit rows into dataclasses.\"\"\"\n    if value in (None, \"\"):\n        return []\n    if isinstance(value, str):\n        value = json.loads(value)\n    if not isinstance(value, list):\n        raise ValueError(\"audit must be a list\")\n\n    rows: list[AuditRow] = []\n    for item in value:\n        if not isinstance(item, dict):\n            raise ValueError(\"audit rows must be objects\")\n        criterion_id = str(item.get(\"criterion_id\") or \"\").strip()\n        result = str(item.get(\"result\") or \"\").strip()\n        if not criterion_id or not result:\n            raise ValueError(\"audit rows require criterion_id and result\")\n        rows.append(\n            AuditRow(\n                criterion_id=criterion_id,\n                result=result,\n                evidence_ids=_coerce_string_list(item.get(\"evidence_ids\")),\n                notes=str(item.get(\"notes\") or \"\"),\n            )\n        )\n    return rows\n\n\ndef _sha256_file(path: Path) -> str:\n    \"\"\"Return the sha256 digest for a local artifact.\"\"\"\n    digest = hashlib.sha256()\n    with path.open(\"rb\") as handle:\n        for chunk in iter(lambda: handle.read(1024 * 1024), b\"\"):\n            digest.update(chunk)\n    return digest.hexdigest()","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/goal_tool.py#L33-L69","documentation":"Each audit row dict must carry non-empty criterion_id and result after stripping. This ensures the audit trail is actually attributable to a criterion. Missing keys, empty strings, or whitespace-only values all trigger it.","triggerScenarios":"audit=[{\"criterion_id\": \"\", \"result\": \"pass\"}] or a row with only notes/evidence but no ids.","commonSituations":"LLM omitting fields it considers obvious; partial dicts merged from templates; frontend forms not enforcing required inputs.","solutions":["Populate criterion_id and result on every row","Skip empty rows before submission: rows=[r for r in rows if r.get('criterion_id') and r.get('result')]","Add client-side required-field validation for the audit form"],"exampleFix":"# before\n{\"criterion_id\": \"c1\"}\n# after\n{\"criterion_id\": \"c1\", \"result\": \"pass\"}","handlingStrategy":"validation","validationCode":"audit = [r for r in audit if str(r.get(\"criterion_id\", \"\")).strip() and str(r.get(\"result\", \"\")).strip()]","typeGuard":"def row_is_complete(r: dict) -> bool:\n    return bool(str(r.get(\"criterion_id\") or \"\").strip() and str(r.get(\"result\") or \"\").strip())","tryCatchPattern":"try:\n    execute(audit=audit)\nexcept ValueError as e:\n    if \"require criterion_id and result\" in str(e):\n        audit = [r for r in audit if row_is_complete(r)]\n        execute(audit=audit)\n    raise","preventionTips":["Filter empty rows before submission","Make criterion_id/result required in form/schema validation"],"tags":["python","validation","required-field","llm-io"],"backgroundTag":"schema-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}