{"record":{"id":"9666e8be9e794d73","repo":"affaan-m/ECC","slug":"explicit-candidate-review-state-required","errorCode":null,"errorMessage":"explicit candidate review state required","messagePattern":"explicit candidate review state required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":297,"sourceCode":"\n\ndef _candidates(items: list, source_hash: str, input_hash: str, source_url: str) -> dict:\n    result = {}\n    for item in items:\n        _object(item, {\"id\", \"media\", \"media_frames\", \"fps\", \"origin\", \"relationship\",\n                       \"source_sha256\", \"compiled_input_sha256\", \"review_status\", \"generation_receipt\"})\n        name = _text(item[\"id\"])\n        if name in result:\n            raise ValueError(\"candidate ids must be unique\")\n        _artifact(item[\"media\"])\n        _integer(item[\"media_frames\"], 1)\n        _rate(item[\"fps\"])\n        if item[\"origin\"] != \"provider_generated\" or item[\"relationship\"] != \"generated_variation\":\n            raise ValueError(\"a generated candidate cannot claim original-source identity\")\n        if item[\"source_sha256\"] != source_hash or item[\"compiled_input_sha256\"] != input_hash:\n            raise ValueError(\"candidate is bound to a different source or input\")\n        if item[\"review_status\"] not in (\"pending\", \"rejected\", \"approved\"):\n            raise ValueError(\"explicit candidate review state required\")\n        evidence = _artifact(item[\"generation_receipt\"], parse_json=True)\n        if not isinstance(evidence, dict) or not isinstance(evidence.get(\"request_id\"), str):\n            raise ValueError(\"historical request evidence required\")\n        _text(evidence[\"request_id\"])\n        expected = {\"source_sha256\": source_hash, \"compiled_input_sha256\": input_hash,\n                    \"candidate_sha256\": item[\"media\"][\"sha256\"], \"source_url\": source_url}\n        if any(evidence.get(key) != value for key, value in expected.items()):\n            raise ValueError(\"historical evidence does not bind the candidate source/input/bytes\")\n        result[name] = item\n    return result\n\n\ndef _inserts(items: list, candidates: dict, config: dict, input_hash: str, edit_hash: str) -> None:\n    occupied = []\n    for item in items:\n        _object(item, {\"candidate_id\", \"candidate_range\", \"timeline_range\", \"retime\", \"approval_file\"})\n        candidate = candidates.get(_text(item[\"candidate_id\"]))\n        if candidate is None or candidate[\"review_status\"] != \"approved\":","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L279-L315","documentation":"_candidates requires each candidate to carry an explicit 'review_status' that is one of 'pending', 'rejected', or 'approved'. Human review gating is central to TasteForge: generated footage may only enter the timeline through a recorded review decision, and a missing or unrecognized status breaks that gate.","triggerScenarios":"Supplying a candidate whose 'review_status' is absent, null, empty, or an unexpected value like 'auto', 'approved_by_me', or 'ok' — anything outside the three allowed literals.","commonSituations":"A review tool that writes its own status vocabulary; candidates exported before the review step ran; hand-crafted candidate entries that omit the field; a schema/version change in the review pipeline.","solutions":["Set review_status to one of exactly \"pending\", \"rejected\", or \"approved\" for each candidate","Run the review step and persist its result into the candidate JSON before building the bundle","Update the review tooling to emit TasteForge's canonical status values"],"exampleFix":"// before\n{\"id\": \"cand-7\", \"review_status\": \"ok\", ...}\n// after\n{\"id\": \"cand-7\", \"review_status\": \"approved\", ...}","handlingStrategy":"validation","validationCode":"const REVIEW_STATUSES = new Set([\"pending\", \"rejected\", \"approved\"]);\nfunction assertReviewStatus(c) {\n  if (!REVIEW_STATUSES.has(c.review_status))\n    throw new Error(`candidate ${c.id} missing explicit review_status`);\n}","typeGuard":"function hasReviewStatus(c) {\n  return c != null && [\"pending\", \"rejected\", \"approved\"].includes(c.review_status);\n}","tryCatchPattern":"try {\n  buildApplicationBundle(cfg);\n} catch (e) {\n  if (e.message.includes(\"explicit candidate review state required\")) {\n    cfg.candidates = cfg.candidates.map(c => ({...c, review_status: c.review_status ?? \"pending\"}));\n    return buildApplicationBundle(cfg);\n  }\n  throw e;\n}","preventionTips":["Default review_status to \"pending\" when creating candidate records so the field is never absent","Keep the review UI's persisted values limited to the three allowed literals","Validate the full candidate schema immediately after the review step writes results"],"tags":["validation","review-workflow","enum"],"backgroundTag":"invalid-enum-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}