Project

General

Profile

Actions

Feature #1525

closed
RA

Feature #1510: Implement internal user roles with ticket progression and assignment controls

Assign route, project developers list, and the can_assign capability flag

Feature #1525: Assign route, project developers list, and the can_assign capability flag

Added by Redmine Admin 4 days ago. Updated 4 days ago.

Status:
Shipped
Priority:
Normal
Assignee:
-
Start date:
07/31/2026
Due date:
% Done:

0%

Estimated time:
spec_ref:

docs/superpowers/specs/2026-07-31-internal-roles-design.md

build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
pr_url:
security_key:
severity:
paused:

Description

**Surface:** backend-only

Expose assignment over the API: a route to set a ticket's assignee, a project-scoped list of assignable developers, and the `can_assign` flag on the ticket payload so the frontend renders from server truth instead of re-deriving the rule.

`can_assign` lives here — shipping it alongside the transition work would have been a flag for a feature that did not exist yet. This subtask needs only the Redmine port work, **not** the transition gate.

Assignment changes no status, so it is deliberately permitted at any stage including Backlog: it cannot bypass the credit gate, and a CTO should be able to pre-assign work before it is paid for.

Per spec decision 6, the developers list is scoped to **project members**, not all internal staff — otherwise the ticket panel becomes a staff directory and tells every tenant who else works here.

## Acceptance criteria
- [ ] `POST /projects/{id}/tasks/{issue_id}/assign` accepts `{"client_id": <int>}` and `{"client_id": null}` (unassign), writes through the Redmine port, and returns the refreshed `TaskDetailOut`.
- [ ] Authorization goes through `may_assign` → 200 for a CTO member and for an admin; 403 for a developer, an ordinary client and any non-member.
- [ ] Target validation: the target must be a member of the same project, have `internal_role == "developer"`, and a non-null `redmine_user_id` — each failure returns a **distinct, actionable 422**; a `client_id` belonging to another tenant is refused without disclosing whether it exists.
- [ ] A foreign or unknown issue 404s via `GetTask` before any write reaches Redmine.
- [ ] A Redmine-side rejection of the id surfaces the mapped 4xx from the adapter subtask — never a 500 (respx returning 422).
- [ ] **HARD:** assigning never changes the status — a Backlog ticket is still status 16 afterwards, and `ticket_charges` is untouched (explicit test).
- [ ] Assigning an already-assigned ticket reassigns idempotently.
- [ ] `GET /projects/{id}/developers` returns `[{client_id, name, email}]` for the project's developer-role members **with a Redmine mapping**, ordered by name; 403 for an ordinary client; a project with no eligible developer returns `[]`, not an error.
- [ ] `TaskDetailOut` gains `can_assign: bool`, true only for cto/admin members.
- [ ] No MCP assign tool in v1 — stated explicitly in the spec so the omission is a decision, not an oversight.
- [ ] `python -m pytest -q` (serial), `ruff check .`, `mypy src` green.

RA Updated by Redmine Admin 4 days ago Actions #1

  • Status changed from Backlog to Submitted
  • spec_ref updated (diff)

RA Updated by Redmine Admin 4 days ago Actions #2

  • Status changed from Submitted to Shipped

Shipped in master build #107 (2026-07-31). `POST …/assign` and `GET …/developers` confirmed live in the production OpenAPI and returning 401 unauthenticated. Note: `DeveloperOut` ships `redmine_user_id` and deliberately NOT `email` — the id is needed because a ticket's assignee is identified by Redmine user id while assignment is written by client id; the email was handing staff addresses to every project member with a role for nothing the UI used.

Actions

Also available in: PDF Atom