feat(assistant): align tool visibility with route audiences

This commit is contained in:
2026-03-30 09:22:26 +02:00
parent 93c4374973
commit a960d43ed1
2 changed files with 83 additions and 0 deletions
@@ -375,6 +375,68 @@ describe("assistant router tool gating", () => {
expect(userNames).not.toContain("get_project_computation_graph"); expect(userNames).not.toContain("get_project_computation_graph");
}); });
it("keeps planning read tools behind the explicit planning permission", () => {
const userWithoutPlanning = getToolNames([], SystemRole.USER);
const userWithPlanning = getToolNames([PermissionKey.VIEW_PLANNING], SystemRole.USER);
expect(userWithoutPlanning).not.toContain("list_allocations");
expect(userWithoutPlanning).not.toContain("list_demands");
expect(userWithoutPlanning).not.toContain("check_resource_availability");
expect(userWithPlanning).toContain("list_allocations");
expect(userWithPlanning).toContain("list_demands");
expect(userWithPlanning).toContain("check_resource_availability");
});
it("keeps controller-only project and dashboard reads hidden from plain users", () => {
const controllerNames = getToolNames([
PermissionKey.VIEW_COSTS,
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
], SystemRole.CONTROLLER);
const userNames = getToolNames([
PermissionKey.VIEW_COSTS,
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
PermissionKey.VIEW_PLANNING,
], SystemRole.USER);
expect(controllerNames).toContain("search_projects");
expect(controllerNames).toContain("get_project");
expect(controllerNames).toContain("get_statistics");
expect(controllerNames).toContain("get_dashboard_detail");
expect(controllerNames).toContain("get_skill_gaps");
expect(controllerNames).toContain("get_project_health");
expect(controllerNames).toContain("get_budget_forecast");
expect(userNames).not.toContain("search_projects");
expect(userNames).not.toContain("get_project");
expect(userNames).not.toContain("get_statistics");
expect(userNames).not.toContain("get_dashboard_detail");
expect(userNames).not.toContain("get_skill_gaps");
expect(userNames).not.toContain("get_project_health");
expect(userNames).not.toContain("get_budget_forecast");
});
it("requires both controller role and advanced assistant access for timeline detail tools", () => {
const controllerWithAdvanced = getToolNames([
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
], SystemRole.CONTROLLER);
const controllerWithoutAdvanced = getToolNames([], SystemRole.CONTROLLER);
const userWithAdvanced = getToolNames([
PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
], SystemRole.USER);
expect(controllerWithAdvanced).toContain("get_timeline_entries_view");
expect(controllerWithAdvanced).toContain("get_timeline_holiday_overlays");
expect(controllerWithAdvanced).toContain("get_project_timeline_context");
expect(controllerWithAdvanced).toContain("preview_project_shift");
expect(controllerWithoutAdvanced).not.toContain("get_timeline_entries_view");
expect(controllerWithoutAdvanced).not.toContain("get_timeline_holiday_overlays");
expect(controllerWithoutAdvanced).not.toContain("get_project_timeline_context");
expect(controllerWithoutAdvanced).not.toContain("preview_project_shift");
expect(userWithAdvanced).not.toContain("get_timeline_entries_view");
expect(userWithAdvanced).not.toContain("get_timeline_holiday_overlays");
expect(userWithAdvanced).not.toContain("get_project_timeline_context");
expect(userWithAdvanced).not.toContain("preview_project_shift");
});
it("keeps timeline write parity tools behind manager/admin role, manageAllocations, and advanced assistant access", () => { it("keeps timeline write parity tools behind manager/admin role, manageAllocations, and advanced assistant access", () => {
const managerNames = getToolNames([ const managerNames = getToolNames([
PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.MANAGE_ALLOCATIONS,
+21
View File
@@ -224,8 +224,26 @@ const COST_TOOLS = new Set([
"find_best_project_resource", "find_best_project_resource",
]); ]);
/** Tools that follow planningReadProcedure access rules in the main API. */
const PLANNING_READ_TOOLS = new Set([
"list_allocations",
"list_demands",
"check_resource_availability",
]);
/** Tools that follow controllerProcedure access rules in the main API. */ /** Tools that follow controllerProcedure access rules in the main API. */
const CONTROLLER_ONLY_TOOLS = new Set([ const CONTROLLER_ONLY_TOOLS = new Set([
"search_projects",
"get_project",
"get_timeline_entries_view",
"get_timeline_holiday_overlays",
"get_project_timeline_context",
"preview_project_shift",
"get_statistics",
"get_dashboard_detail",
"get_skill_gaps",
"get_project_health",
"get_budget_forecast",
"query_change_history", "query_change_history",
"get_entity_timeline", "get_entity_timeline",
"export_resources_csv", "export_resources_csv",
@@ -356,6 +374,9 @@ export function getAvailableAssistantTools(permissions: Set<PermissionKey>, user
if (CONTROLLER_ONLY_TOOLS.has(toolName) && !hasControllerAccess) { if (CONTROLLER_ONLY_TOOLS.has(toolName) && !hasControllerAccess) {
return false; return false;
} }
if (PLANNING_READ_TOOLS.has(toolName) && !permissions.has(PermissionKey.VIEW_PLANNING)) {
return false;
}
if (COST_TOOLS.has(toolName) && !permissions.has(PermissionKey.VIEW_COSTS)) { if (COST_TOOLS.has(toolName) && !permissions.has(PermissionKey.VIEW_COSTS)) {
return false; return false;
} }