Coding Allocation Timeouts
Coding Allocation Timeout Policy
Baseline
DigitVA treats coder and reviewer allocations as time-bound workflow reservations.
Current baseline:
- active coding and reviewing allocations older than
1 hourare considered stale - stale allocations must be released automatically
- stale allocation release must distinguish first-pass coding, recode episodes, and reviewer sessions
Release runs through the Celery beat sweep and, at most once per
CODING_STALE_SWEEP_MIN_INTERVAL_SECONDS (30 s, shared across workers through
the cache), on a coder's allocation request. The throttle only delays the
release of a timed-out case by up to that interval; a case a coder validly
holds (coding_in_progress, coder_step1_saved) is never in the pool, so it
cannot be handed out meanwhile. The throttle also delays the release of the
requesting coder's own allocation if it timed out inside the last 30 s: in
that window a pick of another case gets 409 allocation_exists, and a random
start resumes the timed-out case until the next sweep releases it.
Required behavior — first-pass coder
When a first-pass coding allocation becomes stale:
- deactivate the active
va_allocationsrow for coding - deactivate unfinished active
va_initial_assessmentsrows for the timed-out coder - deactivate first-pass
va_narrative_assessmentsrows for the timed-out coder - deactivate first-pass
va_social_autopsy_analysesrows for the timed-out coder - return canonical workflow state to
ready_for_coding - write an audit log entry for the release
Required behavior — recode
When a recode coding allocation becomes stale:
- deactivate the active
va_allocationsrow for coding - deactivate unfinished active
va_initial_assessmentsrows for the timed-out coder - abandon the active recode episode
- preserve the currently authoritative final COD
- preserve saved NQA and Social Autopsy analysis artifacts from the recode window
- return canonical workflow state to
coder_finalized - write an audit log entry for the release
Required behavior — reviewer session
When a reviewer allocation becomes stale:
- deactivate the active
va_allocationsrow for reviewing - deactivate active
va_reviewer_reviewsrows for the timed-out reviewer - deactivate active
va_narrative_assessmentsrows for the timed-out reviewer - deactivate active
va_social_autopsy_analysesrows for the timed-out reviewer - return canonical workflow state to
reviewer_eligible - write an audit log entry for the release
Rationale: the reviewer final COD submission is the only terminal action for a
reviewer session. The review, NQA and Social Autopsy saves are partial saves.
If the session times out before the final COD is submitted, they are
deactivated and the submission returns to reviewer_eligible so a fresh
reviewer session can start. The reviewer's saved Step 1
(va_reviewer_initial_assessments, masked projects) is not touched: it is
kept, and a fresh session by the same reviewer finds it (owner decision
2026-10-05, reviewer-release-keeps-step1).
Reviewer release
Owner decision 2026-10-05, digitva-xl43. One active reviewing allocation per
reviewer stays. A reviewer may also release their own active reviewing
allocation by choice (POST /api/v1/reviewing/allocation/release), for
example after starting a review of a case they do not want to review.
The effect is the same as the reviewer-session timeout, through
_release_reviewer_allocation in app/services/coding_allocation_service.py:
- the allocation is deactivated
- the reviewer's review, NQA and Social Autopsy analysis are deactivated
- the reviewer's saved Step 1 is kept
- the case returns to
reviewer_eligible - an audit log entry
reviewer_allocation_released_by_revieweris written under the reviewer's user id
Only the allocation's own reviewer may release it, and only while it is active
and the case is in reviewer_coding_in_progress (otherwise 409
no_allocation / wrong_state). The automatic release after the timeout
above stays as it is. The invariant below applies to a reviewer release too.
Coder release
Owner decision 2026-10-05, digitva-xuxk. One active coding allocation per
coder stays. A coder may also release their own active coding allocation by
choice (the "Release" action), for example after taking a case with "Code this
case now" that they do not want to code.
The effect is the same as the timeout release, through
_release_coding_allocation in app/services/coding_allocation_service.py:
- first pass: unfinished Step 1 and first-pass work on that case are
deactivated and the case returns to
ready_for_coding - recode: the episode is abandoned and the case returns to
coder_finalized, with the authoritative final COD kept - an audit log entry is written for the release
Only the allocation's own coder may release it, and only while it is active. The automatic release after the timeout above stays as it is. The invariant below applies to a coder release too.
One active allocation, enforced
Per side since digitva-rcwt: one on the real side and one on the training
side (va_allocation_training, migration d6a1f3b8c2e4).
digitva-igq7. "One active coding allocation per coder" and "one active
reviewing allocation per reviewer" are enforced by the partial unique index
uq_va_allocations_active_to_for on va_allocations (va_allocated_to,
va_allocation_for, va_allocation_training) WHERE va_allocation_status = 'active'. The allocators'
"already holds one?" check is not enough on its own: two concurrent requests
from one user for different cases both passed it. Now the second insert is
refused, its savepoint is rolled back with nothing else written, and the
allocator answers as its check would have: random allocation resumes the held
case; a pick, "Code this case now", or a reviewer start resumes it when it is
the same case and refuses with 409 allocation_exists otherwise; a recode or
demo start refuses.
Migration a7c3e9f1d5b2 resolved existing duplicates before adding the index:
the newest active allocation per (user, kind) stayed active, the others were
marked deactive with an audit row va_allocation_deactivated_duplicate_active
(role vasystem). It did not replay the release's workflow transitions or
artifact deactivations, so a case behind a demoted allocation keeps its
in-progress state; the migration prints the count. Dev had none.
Revocation when a web case is confirmed as a duplicate
Confirming a web case as a duplicate revokes every active coding and
reviewing allocation on its submission at once, with the same release
behaviour as above (first pass, recode, reviewer session), audited as
va_allocation_revoked_duplicate / reviewer_allocation_revoked_duplicate
instead of a timeout. See Coding Workflow State Machine
Policy, "Confirmed Duplicate Cases".
Invariant for all timeout paths
The release action must not:
- delete assessment or review records
- alter submission content
- replace the authoritative final COD with unfinished draft work
Execution model
The timeout release must run in two ways:
- hourly as a scheduled background task (
release_stale_coding_allocations_taskcovers both coder and reviewer allocations) - opportunistically:
- when a coder starts normal coding (
start_codingincoding_service.py) - when a reviewer starts a reviewer session (
start_reviewer_codinginreviewer_coding_service.py)
- when a coder starts normal coding (
This provides both proactive cleanup and local recovery even if the scheduled task has not run yet.