affaan-m/ECC · error · ClaimError
a current bound approved draft is required
Error message
a current bound approved draft is required
What it means
Before granting any permission the library loads the immutable approval snapshot from approval_bound_drafts keyed by (obligation_id, decision_id). If no such row exists, there is no bound, approved draft to dispatch against, so it raises this error. It enforces that only previously approved content can ever be claimed or dispatched.
Solutions
- Verify the draft was recorded: SELECT * FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=? before calling claim()
- Correct the obligation_id/decision_id argument order or values
- Ensure you are connected to the database where the approval actually happened
- Restore the missing snapshot via the trusted decision writer; never hand-insert rows to bypass the approval flow
Example fix
// before
token = claim(db, obligation_id, decision_id, now=ts) # snapshot missing
// after
row = db.execute('SELECT 1 FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=?',
(obligation_id, decision_id)).fetchone()
if row is None:
raise AppError('no approved draft for this decision; run the approval flow first')
token = claim(db, obligation_id, decision_id, now=ts) Defensive patterns
Strategy: validation
Validate before calling
def snapshot_exists(db, oid, did) -> bool:
return db.execute('SELECT 1 FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=?',
(oid, did)).fetchone() is not None Try / catch
try:
token = claim(db, oid, did, now=ts)
except ClaimError as e:
if 'current bound approved draft is required' in str(e):
raise AppError(f'no approved draft for {oid}/{did}; run approval flow first') from e
raise Prevention
- Record the snapshot in approval_bound_drafts before ever calling claim()
- Validate argument order (obligation_id, decision_id) at call sites
- Point at the correct database/environment
- Exclude approval_bound_drafts rows from cleanup/purge jobs, or purge claims with them
When it happens
Trigger: Calling claim(db, obligation_id, decision_id, now=ts) with an obligation_id/decision_id pair never written by the trusted decision writer; calling begin_dispatch() whose claim references a snapshot row that was deleted; typos or wrong IDs (e.g. swapped argument order).
Common situations: Running the claims module against a database where the approval workflow never recorded the draft; pointing at the wrong environment's DB (staging vs production); a migration or cleanup job deleting approval_bound_drafts rows while claims still reference them.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- a top-level committed transaction is required
- approval evidence must bind exact source, candidate and…
- approved hash must be lowercase SHA-256 hexadecimal
- approved text must be valid UTF-8 text
- candidate alias collision with different immutable target
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/29cc9cca8fa42343.
Report an issue: GitHub.
Appendix: source
Thrown at skills/operator-approval-loop/references/approval_claims.py:55
if any(db.execute(f'PRAGMA {name}').fetchone()[0] != 1
for name in ('foreign_keys', 'recursive_triggers')):
raise ClaimError('required SQLite guards are disabled')
try:
db.execute('BEGIN IMMEDIATE')
yield
db.commit()
except BaseException as error:
db.rollback()
if isinstance(error, sqlite3.Error):
raise ClaimError('claim transaction failed; no permission granted') from error
raise
def _snapshot(db, obligation_id, decision_id):
row = db.execute('''SELECT * FROM approval_bound_drafts
WHERE obligation_id=? AND decision_id=?''', (obligation_id, decision_id)).fetchone()
if row is None:
raise ClaimError('a current bound approved draft is required')
try:
digest = hashlib.sha256(row['draft_text'].encode('utf-8')).hexdigest()
except (AttributeError, UnicodeError) as error:
raise ClaimError('approved text must be valid UTF-8 text') from error
stored_digest = row['draft_sha256']
if (not isinstance(stored_digest, str) or len(stored_digest) != 64
or any(character not in '0123456789abcdef' for character in stored_digest)):
raise ClaimError('approved hash must be lowercase SHA-256 hexadecimal')
if not secrets.compare_digest(digest, stored_digest):
raise ClaimError('approved text hash does not match')
return dict(row)
def _claim_row(db, token):
if not isinstance(token, str) or not token:
raise ClaimError('a claim token is required')
row = db.execute('SELECT * FROM obligation_delivery_claims WHERE token=?', (token,)).fetchone()
if row is None:View on GitHub (pinned to 8321021c54)