{"record":{"id":"6ec81761853dc2e3","repo":"affaan-m/ECC","slug":"approved-text-must-be-valid-utf-8-text","errorCode":null,"errorMessage":"approved text must be valid UTF-8 text","messagePattern":"approved text must be valid UTF-8 text","errorType":"validation","errorClass":"ClaimError","httpStatus":null,"severity":"error","filePath":"skills/operator-approval-loop/references/approval_claims.py","lineNumber":59,"sourceCode":"        db.execute('BEGIN IMMEDIATE')\n        yield\n        db.commit()\n    except BaseException as error:\n        db.rollback()\n        if isinstance(error, sqlite3.Error):\n            raise ClaimError('claim transaction failed; no permission granted') from error\n        raise\n\n\ndef _snapshot(db, obligation_id, decision_id):\n    row = db.execute('''SELECT * FROM approval_bound_drafts\n        WHERE obligation_id=? AND decision_id=?''', (obligation_id, decision_id)).fetchone()\n    if row is None:\n        raise ClaimError('a current bound approved draft is required')\n    try:\n        digest = hashlib.sha256(row['draft_text'].encode('utf-8')).hexdigest()\n    except (AttributeError, UnicodeError) as error:\n        raise ClaimError('approved text must be valid UTF-8 text') from error\n    stored_digest = row['draft_sha256']\n    if (not isinstance(stored_digest, str) or len(stored_digest) != 64\n            or any(character not in '0123456789abcdef' for character in stored_digest)):\n        raise ClaimError('approved hash must be lowercase SHA-256 hexadecimal')\n    if not secrets.compare_digest(digest, stored_digest):\n        raise ClaimError('approved text hash does not match')\n    return dict(row)\n\n\ndef _claim_row(db, token):\n    if not isinstance(token, str) or not token:\n        raise ClaimError('a claim token is required')\n    row = db.execute('SELECT * FROM obligation_delivery_claims WHERE token=?', (token,)).fetchone()\n    if row is None:\n        raise ClaimError('unknown claim token')\n    return row\n\n","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/operator-approval-loop/references/approval_claims.py#L41-L77","documentation":"The stored draft_text in the approval snapshot must be valid UTF-8-encodable text; the library hashes it as UTF-8 bytes to compare against the stored SHA-256. If the value is not a string (AttributeError on .encode) or contains unencodable content (UnicodeError), it raises this error rather than proceeding with an unverifiable snapshot.","triggerScenarios":"draft_text stored as bytes, None, or another non-str type in approval_bound_drafts; a surrogate-containing or otherwise invalid text value that fails .encode('utf-8').","commonSituations":"Writing the draft via raw sqlite3 with a BLOB column or bytes value; a different writer/tool storing decoded-with-errors text containing lone surrogates; schema drift where draft_text's declared type changed.","solutions":["Ensure the trusted writer stores draft_text as a Python str (TEXT column)","Decode/re-encode the stored value correctly before the approval flow writes it: text = blob.decode('utf-8')","Inspect the offending row's typeof(draft_text) in SQLite to confirm it is 'text', not 'blob' or 'null'","Re-record the snapshot through the trusted decision writer after fixing the storage layer"],"exampleFix":"// before\ndb.execute('INSERT INTO approval_bound_drafts ... VALUES (?,?)', (oid, did, draft_bytes))  # BLOB\n\n// after\ndb.execute('INSERT INTO approval_bound_drafts ... VALUES (?,?)', (oid, did, draft_bytes.decode('utf-8')))","handlingStrategy":"validation","validationCode":"def draft_text_ok(db, oid, did) -> bool:\n    row = db.execute('SELECT draft_text FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=?',\n                     (oid, did)).fetchone()\n    if not isinstance(row['draft_text'], str):\n        return False\n    try:\n        row['draft_text'].encode('utf-8')\n        return True\n    except UnicodeError:\n        return False","typeGuard":"def is_utf8_text(v) -> bool:\n    return isinstance(v, str) and (lambda: (v.encode('utf-8'), True)[1])() if not any(ord(c) >= 0xD800 and ord(c) <= 0xDFFF for c in v) else False","tryCatchPattern":"try:\n    token = claim(db, oid, did, now=ts)\nexcept ClaimError as e:\n    if 'valid UTF-8 text' in str(e):\n        re_record_snapshot_via_trusted_writer(oid, did)\n    else:\n        raise","preventionTips":["Store draft_text as TEXT via a Python str; never bytes or None","Sanitize text to remove lone surrogates before writing (text.encode('utf-8', errors='strict'))","Check typeof(draft_text)='text' in a data-quality audit","Pin the schema so draft_text cannot drift to BLOB"],"tags":["sqlite","encoding","utf-8"],"backgroundTag":"incompatible-source-type","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"}