Split agent plan permission into read (Planner+) and update (Developer+)
What does this MR do and why?
The agent plan work item widget (Duo workplan) and its GraphQL mutations (workItemEnableAiPlanning, workItemGenerateWorkplan, workItemResumeWorkplan, workItemGenerateReadinessScore) were authorized with update_work_item.
A Guest gets update_work_item on a work item they authored or are assigned to (through the private permissions _update_authored_work_item and _update_assigned_work_item). So such a Guest could read the agent plan, edit it, and start or resume the workplan flow. The flow runs under the flow owner's identity, tokens and push access, so reading and updating the plan should be limited by role.
This MR adds two dedicated granular permissions instead of reusing a broader one. Setting an agent plan starts a flow with the owner's identity and tokens, so it is not the same kind of action as setting assignees or labels, and separate permissions let custom roles grant them on their own.
read_work_item_agent_planis granted to Planner, Reporter and above, for projects and groups. Guests and minimal access users don't get it. It controls who sees the agent plan widget.update_work_item_agent_planis granted to Developer and above, for projects and groups. Planner and Reporter can see the plan but can't edit it or start/resume the flow, as generating a plan requirescreate_duo_workflow_for_ciwhich they don't have either.- The permissions are defined in
config/authz/permissions/work_item_agent_plan/and granted in the role yamls. - The update permission is added to the archived, pending deletion and work items feature permission groups, and the read permission to the work items feature group, so they are blocked in the same cases as
update_work_itemandread_work_itemrespectively. WorkItemPolicyprevents both for hidden work items (unless admin) and for confidential work items the user can't read. The update permission is also prevented for incidents the user can't update, the same asupdate_work_item.
Where they are used:
WorkItems::Widgets::AgentPlan.required_user_abilityreturns:read_work_item_agent_plan, which hides the widget in GraphQL.- The
authorizeof the four mutations,Ai::DuoWorkflows::ResumeWorkplanService#can_resume?andWorkItems::Callbacks::AgentPlanuse:update_work_item_agent_plan. So a Guest can't set an agent plan when creating a work item either (through the agent plan widget input onworkItemCreateor the MCPcreate_work_itemtool).
The granular PAT permission on the mutations (authorize_granular_token permissions: :update_work_item) is unchanged.
The identity the flow runs with when it is resumed by another user is a separate problem and is not changed here. It is tracked in a confidential issue.
The feature is behind the default-off flags workplan (beta), duo_workplan_async_flow and workplan_score (wip), so there is no changelog entry.
The decision log widget has the same gap. It is fixed in a separate MR: !259952
References
How to set up and validate locally
- Enable the flags in the Rails console:
Feature.enable(:workplan)andFeature.enable(:duo_workplan_async_flow). - As a Guest of a project, create an issue (so you are its author).
- Open the GraphQL explorer at
http://gdk.test:3000/-/graphql-explorerand query the issue withworkItem(id:) { widgets { type } }. Check thatAGENT_PLANis not in the list. - Run
workItemGenerateWorkplan(input: { id: ... })as the same Guest. It should return the "resource does not exist or you don't have permission" error. - As the same Guest, run
workItemCreatewithagentPlanWidget: { content: "..." }. Check in the Rails console that the new work item has no agent plan (WorkItem.last.agent_planisnil). - Repeat the widget query as a Planner or Reporter and check that
AGENT_PLANis present, and thatworkItemGenerateWorkplanreturns the permission error. - Repeat the queries as a Developer and check that both work.