{"record":{"id":"ebfc83a57d14efd5","repo":"affaan-m/ECC","slug":"only-a-pre-dispatch-claim-can-be-cancelled","errorCode":null,"errorMessage":"only a pre-dispatch claim can be cancelled","messagePattern":"only a pre-dispatch claim can be cancelled","errorType":"validation","errorClass":"ClaimError","httpStatus":null,"severity":"error","filePath":"skills/operator-approval-loop/references/approval_claims.py","lineNumber":112,"sourceCode":"    \"\"\"\n    with _transaction(db, now):\n        row = _claim_row(db, token)\n        if row['state'] != 'claimed':\n            raise ClaimError('claim cannot grant another dispatch')\n        payload = _snapshot(db, row['obligation_id'], row['decision_id'])\n        changed = db.execute('''UPDATE obligation_delivery_claims SET state='dispatching',updated_ts=?\n            WHERE token=? AND state='claimed' ''', (now, token)).rowcount\n        if changed != 1:\n            raise ClaimError('dispatch transition lost')\n    return payload\n\n\ndef cancel(db, token, *, now):\n    \"\"\"Cancel only a not-yet-dispatched claim. Never reopen its decision key.\"\"\"\n    with _transaction(db, now):\n        row = _claim_row(db, token)\n        if row['state'] != 'claimed':\n            raise ClaimError('only a pre-dispatch claim can be cancelled')\n        db.execute(\"UPDATE obligation_delivery_claims SET state='cancelled',updated_ts=? WHERE token=?\",\n                   (now, token))\n\n\ndef mark_unknown(db, token, *, now):\n    \"\"\"Record uncertainty, including a restarted worker's dispatching claim.\"\"\"\n    with _transaction(db, now):\n        row = _claim_row(db, token)\n        if row['state'] == 'unknown':\n            return\n        if row['state'] != 'dispatching':\n            raise ClaimError('only a dispatched attempt can become unknown')\n        db.execute(\"UPDATE obligation_delivery_claims SET state='unknown',updated_ts=? WHERE token=?\",\n                   (now, token))\n\n\ndef _finish(db, token, coordinate, now, evidence):\n    if not isinstance(coordinate, str) or not coordinate.strip():","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/operator-approval-loop/references/approval_claims.py#L94-L130","documentation":"cancel() raises this ClaimError when the claim is no longer in the pre-dispatch 'claimed' state. Cancellation is only meaningful before a dispatch has been granted; once the claim is 'dispatching', 'delivered', 'unknown', or 'cancelled', the operation is refused so an in-flight or finished delivery can never be retroactively undone.","triggerScenarios":"Calling cancel(db, token) on a token that is already 'dispatching' (begin_dispatch succeeded), 'delivered' (complete/reconcile ran), 'cancelled' (double cancel), or 'unknown' (mark_unknown ran).","commonSituations":"A background worker already picked up the claim while an admin UI tried to cancel it; a job scheduler re-running a cancel step after a first successful cancel; trying to back out a delivery after reconciliation.","solutions":["Check the claim state before cancelling; only call cancel() when state == 'claimed'.","If the claim is 'dispatching', treat the outcome as uncertain: call mark_unknown() and then reconcile() with verified evidence — do not cancel.","If the claim is already 'cancelled', the cancel goal is achieved; catch the ClaimError and treat it as an idempotent no-op.","If it is 'delivered', the delivery is final; use whatever business-level reversal exists outside this module."],"exampleFix":"// before\ndef safe_cancel(db, token, now):\n    claims.cancel(db, token, now=now)  # raises if worker already dispatched\n\n// after\ndef safe_cancel(db, token, now):\n    row = db.execute(\"SELECT state FROM obligation_delivery_claims WHERE token=?\", (token,)).fetchone()\n    if row and row['state'] == 'claimed':\n        claims.cancel(db, token, now=now)\n    # 'cancelled' -> already done; anything else -> cannot cancel","handlingStrategy":"validation","validationCode":"row = db.execute(\"SELECT state FROM obligation_delivery_claims WHERE token=?\", (token,)).fetchone()\nif row is None or row['state'] != 'claimed':\n    return False  # nothing to cancel / not cancellable","typeGuard":null,"tryCatchPattern":"try:\n    claims.cancel(db, token, now=ts)\nexcept claims.ClaimError:\n    log.info(\"claim %s no longer pre-dispatch; cancel skipped\", token)","preventionTips":["Check claim state immediately before cancelling","Make cancel idempotent in caller code: treat already-cancelled as success","Coordinate cancellation UI with the dispatch worker to avoid racing"],"tags":["state-machine","cancellation","claim-token","at-most-once"],"backgroundTag":"invalid-state-transition","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"}