roles: drop the coder's stale task.reassign text, pin the untyped list (#1573)

Row 79 round 1, owner Rocko, candidate manifest 16b12387 on 331d037c.
Approved by Dewey (27611); Sage's gate green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 20:23:35 -05:00
co-authored by Claude Opus 5.5
parent 29124395f4
commit 3780e05df7
4 changed files with 7 additions and 9 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ in this repository, or `$MOSAIC_ROLES_DIR` if set.
"network": "api-only", "network": "api-only",
"authority": { "authority": {
"withinRole": ["task.update.assigned", "git.push.working", "review.request", "message.send"], "withinRole": ["task.update.assigned", "git.push.working", "review.request", "message.send"],
"crossRole": ["task.reassign", "task.scope.change"] "crossRole": ["task.scope.change"]
}, },
"credentials": [ "credentials": [
{ "service": "gitea", "scopes": ["write:issue", "write:repository", "read:user"] }, { "service": "gitea", "scopes": ["write:issue", "write:repository", "read:user"] },
+1 -3
View File
@@ -30,9 +30,7 @@ test("a coder gets no launch, no resolve_decision, and no task tools without a t
const tools = typedTools({ resolved: coder }); const tools = typedTools({ resolved: coder });
assert.deepEqual(names(tools), ["send_message", "raise_decision", "list_agents", "inbox", "trail"]); assert.deepEqual(names(tools), ["send_message", "raise_decision", "list_agents", "inbox", "trail"]);
// Held actions without a tool are listed, so the manifest shows them. // Held actions without a tool are listed, so the manifest shows them.
const untyped = untypedActions(coder, tools); assert.deepEqual(untypedActions(coder, tools), ["task.update.assigned", "git.push.working", "review.request", "task.scope.change"]);
assert.ok(untyped.includes("git.push.working"));
assert.ok(!untyped.includes("message.send"));
}); });
test("a coder with a tracker gets its own task tools and no task_reassign", (t) => { test("a coder with a tracker gets its own task tools and no task_reassign", (t) => {
+4 -4
View File
@@ -190,10 +190,10 @@ Details that aren't obvious from the table:
- `task.close` moves the task into the done bucket. `verdict` is a - `task.close` moves the task into the done bucket. `verdict` is a
citation of the review verdict in the queue. The adapter records it citation of the review verdict in the queue. The adapter records it
and doesn't check it. Checking it against the queue is a follow-up. and doesn't check it. Checking it against the queue is a follow-up.
- A coder role's `crossRole` grant of `task.reassign` can't be used. The - The coder role holds no `task.reassign` grant (row 73). The writer
writer check refuses it before authority is looked at. If a coder check refuses a non-pm `task.reassign` before authority is looked at
should hand work on, that's a change to the field table, not to the anyway. If a coder should hand work on, that's a change to the field
role file. table, not to the role file.
Verbs for one business run one at a time, in the same queue as the poll, Verbs for one business run one at a time, in the same queue as the poll,
so a poll never reads between two writes of a verb. so a poll never reads between two writes of a verb.
+1 -1
View File
@@ -29,7 +29,7 @@ const AUTHORITY = {
withinRole: ['task.create', 'task.assign', 'task.reassign', 'task.schedule', 'task.update.assigned', 'task.close', 'message.send'], withinRole: ['task.create', 'task.assign', 'task.reassign', 'task.schedule', 'task.update.assigned', 'task.close', 'message.send'],
crossRole: ['task.priority.change', 'task.scope.change'], crossRole: ['task.priority.change', 'task.scope.change'],
}, },
coder: { withinRole: ['task.update.assigned', 'message.send'], crossRole: ['task.reassign', 'task.scope.change'] }, coder: { withinRole: ['task.update.assigned', 'git.push.working', 'review.request', 'message.send'], crossRole: ['task.scope.change'] },
reviewer: { withinRole: ['task.update.assigned', 'message.send'], crossRole: [] }, reviewer: { withinRole: ['task.update.assigned', 'message.send'], crossRole: [] },
}; };
const ref = (x) => (x.file ? { file: x.file, expires: x.expires } : { env: x.env, expires: x.expires }); const ref = (x) => (x.file ? { file: x.file, expires: x.expires } : { env: x.env, expires: x.expires });