Skip to content

Task lifecycle and assignment

Workflow tasks (approvals, document submissions, user prompts, etc.) acquired a runtime ownership / hold / comment surface in the LSA-8721 epic. This page summarises the new behaviour so portal approvers, admins, and integration callers can use it without re-reading the implementation PRs (#460, #461, #462, #463).

Lifecycle states

Every workflow_step row now exposes an assignment_state field alongside its existing execution status. The two are independent: the execution status (pending / running / waiting_approval / waiting_response / completed / etc.) is still owned by the engine, while assignment_state describes who is currently working the task in the inbox UI.

assignment_state Meaning
unassigned Nobody owns the task yet. Eligible approvers can self-claim it; admins / task:assign callers can assign it to a specific user.
assigned A user has been assigned but hasn't started yet. Reaching the task as that user shows the row in their inbox.
in_progress The current assignee has actively claimed the task and is working it. Other users (even other eligible approvers) cannot complete / respond / approve until ownership changes.
on_hold The task is paused with an optional explanation. Non-admin callers cannot complete / respond / approve until the hold is released.

Transitions:

  • unassignedin_progress via POST /api/tasks/:id/claim.
  • unassigned / assigned / in_progressassigned via POST /api/tasks/:id/assign (requires task:assign).
  • any non-terminal → unassigned via POST /api/tasks/:id/unassign (requires task:assign).
  • any non-on_holdon_hold via POST /api/tasks/:id/hold (the assignee or a task:assign caller).
  • on_holdassigned (or unassigned if no assignee remains) via POST /api/tasks/:id/unhold.

Terminal execution statuses (completed, failed, skipped, cancelled) freeze the lifecycle — the assignment fields become informational and the lifecycle endpoints return 409 for further mutations.

Approval semantics are unchanged

Runtime claiming is an authorization boundary, not a change to the existing approval-completion model. A multi-row approval step (approvers: ["user:abc", "group:helpdesk"]) still finalises only when EVERY row reaches a non-pending decision — the AND-of-list contract enforced by the workflow engine. Claiming the task simply prevents two eligible approvers from racing each other on the same row — once user A claims, user B sees a 403 on POST /api/approvals/:id/decide until the claim is released or the task is reassigned.

Multi-approver "any-of" / quorum semantics are tracked in issue #237 and are explicitly not delivered by LSA-8721.

Comments

Each task row has its own comment thread under /api/tasks/:id/comments. Visibility and moderation are split into two orthogonal capabilities:

  • canViewDeleted — see soft-deleted rows (with the body redacted to null). Admin, task:assign, and task:comment_manage callers all qualify.
  • canModerate — edit or soft-delete comments authored by other users. Admin and task:comment_manage only. The author can always edit / soft-delete their own comment.

Comment bodies persist on the row but are intentionally never copied into audit metadata. Audit entries (task.comment_added, task.comment_edited, task.comment_deleted) carry the task id, an actor, a bodyLength, and a byAuthor flag — never the body itself.

Permissions

Two permissions were added in PR #460 and are seeded automatically:

  • task:assign — assign / reassign / unassign / hold / release- hold any task. Granted to admin (every permission) and resource_manager.
  • task:comment_manage — moderate comments authored by other users. Granted to admin and resource_manager.

approver and requestor roles only get the existing task:complete permission and rely on assignee or eligibility checks for action authorization.

Audit actions

The following audit actions were introduced for the lifecycle epic:

  • task.claimed / task.assigned / task.unassigned
  • task.held / task.released
  • task.comment_added / task.comment_edited / task.comment_deleted

All eight redact user-typed free-text content (hold reasons, comment bodies) from metadata; only identifiers, lengths, and a byAuthor flag are persisted.

UI surface

  • The admin task inbox (/tasks in the admin web app) shows a comments icon and a per-row action menu offering Claim / Assign / Reassign / Unassign / Hold / Release hold next to the existing Complete / Delete affordances.
  • The portal task inbox (/tasks in the portal web app) shows the same comments icon and a narrower action menu offering Claim / Place on hold / Release hold. Portal users self-claim and don't see the user-picker assign affordance.