{"record":{"id":"68b4d07765d7636e","repo":"affaan-m/ECC","slug":"approved-text-hash-does-not-match","errorCode":null,"errorMessage":"approved text hash does not match","messagePattern":"approved text hash does not match","errorType":"validation","errorClass":"ClaimError","httpStatus":null,"severity":"critical","filePath":"skills/operator-approval-loop/references/approval_claims.py","lineNumber":65,"sourceCode":"            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\ndef claim(db, obligation_id, decision_id, *, now):\n    \"\"\"Reserve one already-authorized decision; return only a random claim token.\"\"\"\n    with _transaction(db, now):\n        _snapshot(db, obligation_id, decision_id)\n        token = secrets.token_hex(32)\n        db.execute('''INSERT INTO obligation_delivery_claims","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/operator-approval-loop/references/approval_claims.py#L47-L83","documentation":"After recomputing SHA-256 over the stored draft_text, the library compares it to the stored draft_sha256 using a constant-time comparison. A mismatch means the approved text was altered, corrupted, or the hash refers to different content, so the library refuses to grant any dispatch permission — this is the integrity check protecting against post-approval tampering.","triggerScenarios":"draft_text was modified after the hash was recorded (hand-edit, re-encoding/normalization like newline or encoding changes, whitespace trimming); the hash was computed over different bytes than stored; two rows wrote inconsistent text/hash pairs.","commonSituations":"A tool rewriting the draft with normalized line endings or encoding after approval; a migration re-encoding text (e.g. adding/removing a BOM); copying rows between databases and updating only one column.","solutions":["Recompute the hash over the current draft_text and re-record it through the trusted approval writer, or re-approve the (correct) content","Audit what changed: compare the stored hash to sha256 of the current text and diff against the original approved draft from your approval log","Never edit approval_bound_drafts.draft_text in place after approval; write a new snapshot instead","Ensure all writers hash exactly the same bytes that are stored (same encoding, no preprocessing)"],"exampleFix":"// before\ntext = row['draft_text'].replace('\\r\\n', '\\n')  # altered after hashing\n...\n# ClaimError: approved text hash does not match\n\n// after\n# re-approve and record a fresh snapshot\ntext = normalized_text\nhash_hex = hashlib.sha256(text.encode('utf-8')).hexdigest()\nwriter.record_snapshot(oid, did, text, hash_hex)","handlingStrategy":"try-catch","validationCode":"import hashlib, secrets\n\ndef snapshot_intact(db, oid, did) -> bool:\n    row = db.execute('SELECT draft_text, draft_sha256 FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=?',\n                     (oid, did)).fetchone()\n    digest = hashlib.sha256(row['draft_text'].encode('utf-8')).hexdigest()\n    return secrets.compare_digest(digest, row['draft_sha256'])","typeGuard":null,"tryCatchPattern":"try:\n    token = claim(db, oid, did, now=ts)\nexcept ClaimError as e:\n    if 'hash does not match' in str(e):\n        alert_tampering(oid, did)  # do NOT auto-fix; escalate to re-approval\n    else:\n        raise","preventionTips":["Treat approval_bound_drafts as immutable after approval; write new snapshots instead of editing","Recompute and compare hashes in monitoring jobs to detect drift early","Ensure every writer hashes exactly the stored bytes (same encoding, no transformations)","Keep an approval log outside the DB to diff text changes against"],"tags":["integrity","tampering","sha256"],"backgroundTag":"checksum-mismatch","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"}