tasks, webui: the task field-writer table as data (#1550)

Row 61. packages/tasks/fields.json holds the slice 1 field table in two
layers (decision 113): each broker task verb's writer check, and each
field's writing verbs and Vikunja calls. packages/tasks/src/fields.mjs
checks the shape as code and loads it frozen. The tasks test reads the
writer layer from the table and authority from roles/*.json and drives
every task verb against the fake Vikunja. The Console serves the table
at GET /api/task-fields, and the task page shows a "Who may write"
section. The slice 1 doc's field table points to the file.

Darkwing's round 1, candidate manifest 33b96986, base 4f8ac1e7. Approved
by Dewey (27776) and Filbert (27785). Sage's gate green 18:57:31Z to
19:01:58Z.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-12 14:02:56 -05:00
co-authored by Claude Opus 5.5
parent 70ed77da34
commit 6c42344d9d
12 changed files with 519 additions and 12 deletions
+15 -6
View File
@@ -63,17 +63,26 @@ The title, description and comments rows were corrected to what the code
enforces on 2026-10-11 (lead decision 90). `message.send` is a bus
message and never writes a tracker comment.
The title, description, due date, priority, assigned-role and `done` rows
were corrected to what the code enforces on 2026-10-12 (lead decision
113). Row 61 holds the table as data in `packages/tasks/fields.json`: the
writer each verb checks (pm, the assigned role or any role) and the
Vikunja calls it makes. Whether a write also needs a resolved decision
comes from the role's authority in `roles/*.json`, and the row's test
reads it from there.
| Field | Writer | Action | Vikunja call |
|---|---|---|---|
| title, description | pm at create; afterwards a role whose authority lists `task.scope.change` (pm, cto and coder today, each cross-role), with a resolved decision | `task.create`, `task.scope.change` | `POST /projects/{p}/tasks`; `PATCH /tasks/{t}` |
| due date, priority | pm | `task.schedule`, `task.priority.change` | `PATCH /tasks/{t}` |
| percent done | the assigned role | `task.update.assigned` | `PATCH /tasks/{t}` |
| state (bucket) other than `done` | the assigned role | `task.update.assigned` | `PUT /projects/{p}/views/{k}/buckets/{b}/tasks` |
| `done` | pm, after a review verdict | `task.close` | the same move, into the `done` bucket |
| title, description | pm at create; afterwards any role through `task.scope.change`, with a resolved decision: from the arbiter for a role that lists it cross-role (pm, cto and coder today), from Jason for any other | `task.create`, `task.scope.change` | `POST /projects/{p}/tasks`; `PATCH /tasks/{t}` |
| due date | pm | `task.create`, `task.schedule` | `POST /projects/{p}/tasks`; `PATCH /tasks/{t}` |
| priority | pm; a change after create needs a resolved decision (cross-role) | `task.create`, `task.priority.change` | `POST /projects/{p}/tasks`; `PATCH /tasks/{t}` |
| percent done | the assigned role; a role without `task.update.assigned` needs Jason's decision | `task.update.assigned` | `PATCH /tasks/{t}` |
| state (bucket) other than `done` | the assigned role; a role without `task.update.assigned` needs Jason's decision | `task.update.assigned` | `PUT /projects/{p}/views/{k}/buckets/{b}/tasks` |
| `done` | pm, citing a review verdict (recorded, not checked) | `task.close` | the same move, into the `done` bucket |
| assignee | pm | `task.assign`, `task.reassign` | `POST /tasks/{t}/assignees`; `DELETE /tasks/{t}/assignees/{u}` |
| labels | pm | `task.create`, `task.schedule` | `POST /tasks/{t}/labels`; `DELETE /tasks/{t}/labels/{l}` |
| relations | pm | `task.create`, `task.schedule` | `POST /tasks/{t}/relations`; `DELETE /tasks/{t}/relations/{kind}/{other}` |
| comments | the assigned role, append only | `task.update.assigned` | `POST /tasks/{t}/comments` |
| comments | the assigned role, append only; a role without `task.update.assigned` needs Jason's decision | `task.update.assigned` | `POST /tasks/{t}/comments` |
A `PATCH` body carries only `title`, `description`, `due_date`,
`priority` and `percent_done`, and only the keys the action owns. Vikunja