---
id: WF-CRM-ACT-004
title: Claim and release shared work
trigger: >-
  A user claims or releases shared queued work in the console, or claims eligible
  role-offered work through the API.
product_area: crm
module: activities-tasks
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-CRM-ACT-012
  - CAP-CRM-ACT-013
  - CAP-CRM-ACT-015
moves:
  - STATE-CRM-ACT-003
  - STATE-CRM-ACT-004
crosses:
  - SVC-CRM-ACT-tasks
idempotency: >-
  Claims and releases carry neither a read version nor a request identifier. A repeated
  claim cannot replace an existing holder, and a repeated release has no holder to clear.
  These actions therefore return refusals rather than a repeat-success result.
concurrency: >-
  A conditional queue update admits at most one committed claimant for available work. A
  role-only claim locks the record before checking current role authority. Attention or
  other transaction failures can prevent any contender from committing.
hops:
  - 'n': 1
    actor_or_service: Queued-work list and drawer
    action: >-
      Expose Claim and Release for eligible queued work to task-update users and invoke
      the selected bodyless action.
    anchor: 'react-console:components/tasks/task-detail-sheet.tsx:taskMenuItems'
    seam: sync
    on_failure: >-
      The console renders claim conflicts as a generic someone-else message; failed
      actions do not guarantee refreshed detail.
    left_inconsistent: The displayed holder can remain stale after a rejected action.
    observer: The user sees action feedback.
    provenance: test
    source: >-
      react-console:lib/tasks/types.test.ts#is offered on an open task waiting unclaimed
      in a queue
  - 'n': 2
    actor_or_service: Tasks API
    action: Require task-update authority for claiming or releasing.
    anchor: 'services-crm:modules/tasks/aws/api/tasks.routes.ts:tasksRoutes'
    seam: sync
    on_failure: Return forbidden when the authority is absent.
    left_inconsistent: No write proceeds.
    observer: The caller receives the refusal.
    provenance: test
    source: >-
      services-crm:modules/tasks/tests/e2e/tasks.e2e.spec.js#answers 403 to a caller who
      may only read tasks claiming one
  - 'n': 3
    actor_or_service: Conditional queue claim
    action: >-
      Require a user caller and conditionally assign unfinished, unlocked, queued and
      unheld work without changing queue arrival.
    anchor: 'services-crm:modules/tasks/services/persistence/tasks.ts:claimTask'
    seam: sync
    on_failure: >-
      If nothing matches, reread to distinguish absence, locks, closure, routing or an
      existing holder.
    left_inconsistent: No failed conditional claim changes the record.
    observer: The caller receives the service’s specific refusal.
    provenance: test
    source: >-
      services-crm:modules/tasks/tests/integration/services/persistence/tasks.concurrency.integration.spec.js#lets
      exactly one of them have it
  - 'n': 4
    actor_or_service: Locked role-offer claim
    action: >-
      For an unqueued role offer, lock the record, check unfinished and unheld standing,
      and ask Platform whether the user currently holds its role.
    anchor: 'services-crm:modules/tasks/services/persistence/tasks.ts:claimOfferedTask'
    seam: sync
    on_failure: >-
      Refuse an ineligible user or propagate an authority-service failure without
      assigning the task.
    left_inconsistent: The role offer remains unchanged.
    observer: The claiming caller receives the response.
    provenance: test
    source: >-
      services-crm:modules/tasks/tests/integration/services/persistence/tasks.role-claim.integration.spec.js#is
      refused 403 with the code task_claim_not_role_holder, and the task stays offered
  - 'n': 5
    actor_or_service: Platform role-holder query
    action: >-
      Delegate the current role-holder query to Platform security, using the documented
      scope and active unconditional assignment policy.
    anchor: 'services-crm:modules/tasks/services/persistence/tasks.ts:holdsRole'
    seam: sync
    on_failure: A denied answer or failed lookup prevents the claim.
    left_inconsistent: Local claim persistence has not committed.
    observer: The tasks service receives the authority result; external policy proof remains open.
    provenance: documented
    source: 'services-crm:docs/workstreams/crm-tasks-role-claim.md#What changed'
  - 'n': 6
    actor_or_service: Conditional shared-work release
    action: >-
      Conditionally clear the holder and assignment stamps on unfinished, unlocked work
      that retains a queue or candidate role.
    anchor: 'services-crm:modules/tasks/services/persistence/tasks.ts:releaseTask'
    seam: sync
    on_failure: >-
      A nonmatching update returns the corresponding absence, lock, lifecycle or routing
      refusal.
    left_inconsistent: A rejected release preserves the holder.
    observer: The release caller receives the result.
    provenance: test
    source: >-
      services-crm:modules/tasks/tests/integration/services/persistence/tasks.integration.spec.js#leaves
      the task in its queue, in the place it had: a release is not a re-queue
  - 'n': 7
    actor_or_service: Task attention synchronization
    action: >-
      Update enabled overdue attention for the new holder, or for queue responsibility and
      administrator fallback after release.
    anchor: >-
      services-crm:modules/tasks/services/persistence/task-attention-items.ts:syncTaskOverdueAttention
    seam: sync
    on_failure: An enabled publication failure rolls back the claim or release.
    left_inconsistent: Task routing and its own attention update share commit fate.
    observer: The caller and Platform attention expose successful changes.
    provenance: observed
    source: >-
      services-crm:modules/tasks/services/persistence/task-attention-items.ts:overdueTaskAudience
branches:
  - >-
    A task retaining both queue and role routing uses queue claiming without a role-holder
    check.
  - 'Any task-update caller can release eligible work, including a service caller.'
  - Queue retirement does not prevent claims in the current implementation.
waits: []
evidence:
  - >-
    services-crm at a8d1364f: modules/tasks/services/persistence/tasks.ts (claimTask,
    claimOfferedTask, lockOfferedTask, holdsRole, releaseTask, claimRefusal,
    releaseRefusal); services/tasks/task-refusals.ts;
    docs/workstreams/crm-tasks-role-claim.md.
  - >-
    react-console at baf6dae3: lib/api/tasks.ts (claimTask, TaskClaimConflictError);
    lib/tasks/types.ts (canClaim, canRelease); components/tasks/task-detail-sheet.tsx.
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004]
---

# Claim and release shared work

## What It Does

Shared work is taken through a conditional assignment rather than a preliminary availability read. Queue and role-only claims have distinct authorization paths. Release clears the holder while preserving the shared route and queue arrival where present.

## The Flow

```mermaid
flowchart TD
  A[Authorize shared-work action] --> B{Claim or release?}
  B -->|Release| C[Conditionally clear holder and preserve routing]
  B -->|Claim| D{Queue present?}
  D -->|Yes| E[Conditionally claim available queued work]
  D -->|No role offer| F[Reject unrouted claim]
  D -->|Role only| G[Lock task and check current role authority]
  G --> H[Assign eligible user]
  C --> I[Synchronize enabled attention and commit]
  E --> I
  H --> I
```

The numbered hops specify the request, persistence and integration boundaries in order. Each hop identifies its evidence anchor, synchronization seam and observable failure outcome. Branches distinguish optional and delegated paths.

## When Something Fails

A lost race leaves the winner’s assignment intact and returns a conflict. The console simplifies claim conflicts and can hide the service’s actual lock or lifecycle reason. Role lookup and enabled attention failures also prevent claim commitment, so successful arbitration alone is not a guarantee of a committed winner.

## Open Design Questions

Role-offer discovery in the console remains unresolved. Platform’s actual security implementation is outside these source pins, and inactive queue behavior still needs its planning decision.
