Project

General

Profile

Actions

Feature #1543

closed
RA

Feature #1511: Replace ticket view with Kanban board for CTO and assigned developers

API: per-viewer visibility matrix and per-card drag capability

Feature #1543: API: per-viewer visibility matrix and per-card drag capability

Added by Redmine Admin 1 day ago. Updated 1 day ago.

Status:
Shipped
Priority:
Normal
Assignee:
-
Start date:
08/03/2026
Due date:
% Done:

0%

Estimated time:
spec_ref:

docs/superpowers/specs/2026-08-03-kanban-board-design.md

build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
feat/1543-board-visibility-matrix
pr_url:
https://github.com/omdev-tech/PipeLiner-Client/pull/97
security_key:
severity:
paused:

Description

**Surface:** backend (`api/`)
**Depends on:** "API: assignee, priority and parent on the task list payload"
**Blocks:** "Frontend: Kanban board replaces the ticketing list view"

Authorization work, split out from the payload change on purpose: a mistake here leaks one developer's tickets to another, or one tenant's to another. Separate review, separate test suite.

Apply the PO's visibility matrix (decision **D2**) in `ListProjectTasks` (`application/task_board.py:86`), and attach each card's transition capability **in one pass over the list** — computed by the same progression rules that back the generic transition route and MCP `transition_ticket`, so the three can never disagree.

### The visibility matrix

| Viewer | Cards visible | Drag |
|---|---|---|
| Ordinary client (`internal_role: null`) | **all** tickets in their project | none — read-only board |
| Developer (`internal_role: "developer"`) | **only** tickets assigned to them | their single legal forward step |
| CTO (`internal_role: "cto"`) / admin (`is_admin`) | all tickets | full server-allowed set, backward included |

**Deliberate and not a bug: a developer sees fewer cards than the client does.** The client is the payer and owns the whole project view; a developer's board is their personal work queue. Do not widen the developer scope in review.

Reuse the shipped roles foundation (#1527/#1528) — `internal_role`, `can_move_forward`, `next_status_id`, server-narrowed `allowed_statuses`, `is_assignee` — rather than adding a parallel role check. The frontend must be able to derive drag rights from the payload alone, so **no role name and no status-order array may cross the wire** for the client to interpret (the internal-roles rule, `docs/superpowers/specs/2026-07-31-internal-roles-design.md`).

## Acceptance criteria
- [ ] **Parametrised test over every viewer kind** (ordinary client, developer-assigned, developer-not-assigned, CTO, admin) asserting the exact visible ticket set of the matrix above. A new role added without a matrix entry fails the test.
- [ ] Every card carries `can_move_forward`, `next_status_id` and server-narrowed `allowed_statuses`, matching what `_detail_out` (`tasks.py:672`) returns for the same ticket and the same viewer.
- [ ] **No existence leak:** a developer requesting a ticket assigned to someone else receives a response byte-identical in status and body to a non-member requesting the same ticket — asserted in a single test comparing both.
- [ ] **Computed once for the whole list:** a call-count / query-count assertion proves no per-ticket detail call and no N+1 as the list grows.
- [ ] **Parametrised over every viewer kind:** status `16` (Backlog) and status `14` (Blocked) never appear in `allowed_statuses` nor as `next_status_id` — consistent with `FORWARD_ORDER` (`domain/tasks/progression.py:78`) and `may_set_status`, which refuses Backlog in both directions even for a CTO.
- [ ] **Credit gate intact:** for a ticket *in* Backlog, `can_move_forward` is false for every viewer kind, and the generic transition route still refuses it with `BacklogNotSubmitted` → 409 (regression test). The board must not become a free way past the gate.
- [ ] **MCP parity:** for a given actor and ticket, `allowed_statuses` on the board equals exactly what MCP `transition_ticket` advertises for that same actor and ticket — preserving the standing rule that MCP grants no capability a browser session lacks, and vice versa.
- [ ] An ordinary client's cards all carry `can_move_forward: false` and an empty `allowed_statuses`.
- [ ] The payload contains no role name, role predicate or status-order array for the frontend to interpret.
- [ ] Gates green: `python -m pytest -q` (**run serially**), `ruff check .`, `mypy src`.

Actions

Also available in: PDF Atom