affaan-m/ECC · error · ClaimError

obligation completion failed

Error message

obligation completion failed

What it means

_finish raises this when the final guarded UPDATE obligations SET status='sent' WHERE id=? AND status='approved' affected a rowcount other than 1. The obligation is no longer in 'approved' status at completion time (or was deleted), so recording the delivery would contradict the obligation lifecycle. The whole transaction is rolled back.

Solutions

  1. Re-verify the obligation is still 'approved' before finishing: SELECT status FROM obligations WHERE id=?; if not, abandon the claim (mark_unknown for a dispatched attempt) rather than completing.
  2. Shorten the window between claim() and completion; complete promptly after dispatch.
  3. Ensure all writers to the obligations table go through the guarded, status-checked updates this module uses.
  4. If the row was deleted, investigate the deleting writer; the claim cannot be finished against a missing obligation.

Example fix

// before: finishing a stale claim after revocation
claims.complete(db, stale_token, coordinate, now=ts)  # obligation no longer 'approved'

// after
status = db.execute("SELECT status FROM obligations WHERE id=?", (obligation_id,)).fetchone()['status']
if status == 'approved':
    claims.complete(db, token, coordinate, now=ts)
else:
    claims.mark_unknown(db, token, now=ts)  # or handle revocation path
Defensive patterns

Strategy: validation

Validate before calling

ob = db.execute("SELECT status FROM obligations WHERE id=?", (obligation_id,)).fetchone()
if ob is None or ob['status'] != 'approved':
    raise ValueError("obligation no longer approved; do not complete the claim")

Try / catch

try:
    claims.complete(db, token, coordinate, now=ts)
except claims.ClaimError:
    claims.mark_unknown(db, token, now=ts)  # dispatched but no longer completable

Prevention

When it happens

Trigger: The obligation's status was changed from 'approved' (e.g. to a revoked/expired/withdrawn state) between claim() and complete()/reconcile(); the obligation row was deleted by another writer; another module updated status concurrently.

Common situations: A long-lived claim outliving an approval revocation; an admin tool or other workflow mutating the obligations table outside this module; a restarted pipeline finishing a stale claim days later.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/aad9ee3faa54390c. Report an issue: GitHub.

Appendix: source

Thrown at skills/operator-approval-loop/references/approval_claims.py:154

                             (row['obligation_id'], row['decision_id'])).fetchone()
        if row['state'] == 'delivered':
            if receipt is None or receipt['coordinate'] != coordinate or receipt['kind'] != 'draft_sent':
                raise ClaimError('completion contradicts the existing receipt')
            return False
        expected_state = 'dispatching' if evidence is None else 'unknown'
        if row['state'] != expected_state:
            raise ClaimError('completion requires the correct dispatch/reconciliation state')
        _snapshot(db, row['obligation_id'], row['decision_id'])
        db.execute('''INSERT INTO obligation_deliveries
            (obligation_id,decision_id,kind,coordinate,delivered_ts) VALUES (?,?,'draft_sent',?,?)''',
                   (row['obligation_id'], row['decision_id'], coordinate, now))
        db.execute('''UPDATE obligation_delivery_claims
            SET state='delivered',updated_ts=?,reconciliation_evidence=? WHERE token=?''',
                   (now, evidence, token))
        changed = db.execute("UPDATE obligations SET status='sent' WHERE id=? AND status='approved'",
                             (row['obligation_id'],)).rowcount
        if changed != 1:
            raise ClaimError('obligation completion failed')
    return True


def complete(db, token, coordinate, *, now):
    """Atomically record a confirmed result; identical duplicate completion is a no-op."""
    return _finish(db, token, coordinate, now, None)


def reconcile(db, token, coordinate, evidence, *, now):
    """Trusted caller supplies verified outcome evidence; this does not verify it.

    No cancellation/retry of unknown claims is provided: a paused original
    executor could still act. Operator authentication is outside this reference.
    """
    if not isinstance(evidence, str) or not evidence.strip():
        raise ClaimError('trusted reconciliation evidence is required')
    return _finish(db, token, coordinate, now, evidence)

View on GitHub (pinned to 8321021c54)