feat(platform): checkpoint current implementation state
This commit is contained in:
@@ -1345,6 +1345,15 @@ function getTrpcValidationIssues(error: TRPCError): Array<{
|
||||
function toAssistantUserResourceLinkError(
|
||||
error: unknown,
|
||||
): AssistantToolErrorResult | null {
|
||||
if (error instanceof TRPCError && error.code === "CONFLICT") {
|
||||
if (error.message.includes("already linked")) {
|
||||
return { error: "Resource is already linked to another user." };
|
||||
}
|
||||
if (error.message.includes("changed during update")) {
|
||||
return { error: "Resource link changed during update. Please retry." };
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
|
||||
if (error.message.includes("Resource")) {
|
||||
return { error: "Resource not found with the given criteria." };
|
||||
@@ -1478,6 +1487,9 @@ function toAssistantTaskActionError(
|
||||
if (error.message.includes("already completed")) {
|
||||
return { error: "Task is already completed." };
|
||||
}
|
||||
if (error.message.includes("dismissed")) {
|
||||
return { error: "Task has been dismissed and cannot be executed." };
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof TRPCError && error.code === "BAD_REQUEST") {
|
||||
@@ -1812,6 +1824,14 @@ function toAssistantNotificationCreationError(
|
||||
return { error: "No recipients matched the broadcast target." };
|
||||
}
|
||||
|
||||
if (
|
||||
context === "broadcast"
|
||||
&& trpcError?.code === "BAD_REQUEST"
|
||||
&& trpcError.message === "Scheduled broadcasts with task metadata are not supported yet."
|
||||
) {
|
||||
return { error: "Scheduled broadcasts with task metadata are not supported yet." };
|
||||
}
|
||||
|
||||
if (trpcError?.code === "NOT_FOUND") {
|
||||
if (trpcError.message.includes("broadcast")) {
|
||||
return { error: "Broadcast not found with the given criteria." };
|
||||
@@ -2048,6 +2068,128 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
|
||||
...settingsAdminToolDefinitions,
|
||||
], LEGACY_MONOLITHIC_TOOL_ACCESS);
|
||||
|
||||
const TOOL_DEFINITIONS_BY_NAME = new Map(
|
||||
TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool]),
|
||||
);
|
||||
|
||||
type AssistantToolAccessEvaluationContext = Pick<ToolContext, "permissions" | "userRole">;
|
||||
|
||||
type AssistantToolAccessFailure =
|
||||
| { type: "role" }
|
||||
| {
|
||||
type: "permission";
|
||||
permission?: PermissionKey;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
function hasAssistantResourceOverviewAccess(
|
||||
permissions: Set<PermissionKey>,
|
||||
): boolean {
|
||||
return permissions.has(PermissionKey.VIEW_ALL_RESOURCES)
|
||||
|| permissions.has(PermissionKey.MANAGE_RESOURCES);
|
||||
}
|
||||
|
||||
function getAssistantToolAccessRequirements(
|
||||
tool: ToolDef,
|
||||
): ToolAccessRequirements | undefined {
|
||||
return tool.access ?? LEGACY_MONOLITHIC_TOOL_ACCESS[tool.function.name];
|
||||
}
|
||||
|
||||
function getAssistantToolAccessFailure(
|
||||
tool: ToolDef,
|
||||
ctx: AssistantToolAccessEvaluationContext,
|
||||
): AssistantToolAccessFailure | null {
|
||||
const access = getAssistantToolAccessRequirements(tool);
|
||||
if (!access) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
access.allowedSystemRoles
|
||||
&& !access.allowedSystemRoles.includes(ctx.userRole as SystemRole)
|
||||
) {
|
||||
return { type: "role" };
|
||||
}
|
||||
|
||||
const missingRequiredPermission = access.requiredPermissions?.find(
|
||||
(permission) => !ctx.permissions.has(permission),
|
||||
);
|
||||
if (missingRequiredPermission) {
|
||||
return {
|
||||
type: "permission",
|
||||
permission: missingRequiredPermission,
|
||||
};
|
||||
}
|
||||
|
||||
if (access.requiresPlanningRead && !ctx.permissions.has(PermissionKey.VIEW_PLANNING)) {
|
||||
return {
|
||||
type: "permission",
|
||||
permission: PermissionKey.VIEW_PLANNING,
|
||||
};
|
||||
}
|
||||
|
||||
if (access.requiresCostView && !ctx.permissions.has(PermissionKey.VIEW_COSTS)) {
|
||||
return {
|
||||
type: "permission",
|
||||
permission: PermissionKey.VIEW_COSTS,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
access.requiresAdvancedAssistant
|
||||
&& !ctx.permissions.has(PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS)
|
||||
) {
|
||||
return {
|
||||
type: "permission",
|
||||
permission: PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
access.requiresResourceOverview
|
||||
&& !hasAssistantResourceOverviewAccess(ctx.permissions)
|
||||
) {
|
||||
return {
|
||||
type: "permission",
|
||||
message: "Permission denied: you need resource overview access to perform this action.",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function toAssistantToolAccessError(
|
||||
failure: AssistantToolAccessFailure,
|
||||
): AssistantVisibleError {
|
||||
if (failure.type === "role") {
|
||||
return new AssistantVisibleError("You do not have permission to perform this action.");
|
||||
}
|
||||
|
||||
if (failure.permission) {
|
||||
return new AssistantVisibleError(
|
||||
`Permission denied: you need the "${failure.permission}" permission to perform this action.`,
|
||||
);
|
||||
}
|
||||
|
||||
return new AssistantVisibleError(
|
||||
failure.message ?? "You do not have permission to perform this action.",
|
||||
);
|
||||
}
|
||||
|
||||
export function canAccessAssistantTool(
|
||||
tool: ToolDef,
|
||||
ctx: AssistantToolAccessEvaluationContext,
|
||||
): boolean {
|
||||
return getAssistantToolAccessFailure(tool, ctx) === null;
|
||||
}
|
||||
|
||||
export function getAvailableAssistantToolsForContext(
|
||||
permissions: Set<PermissionKey>,
|
||||
userRole: string,
|
||||
): ToolDef[] {
|
||||
return TOOL_DEFINITIONS.filter((tool) => canAccessAssistantTool(tool, { permissions, userRole }));
|
||||
}
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Resolve a responsible person name against existing resources. Returns the exact displayName or an error object. */
|
||||
@@ -2285,6 +2427,7 @@ const executors = {
|
||||
...createUserSelfServiceExecutors({
|
||||
createUserCaller,
|
||||
createScopedCallerContext,
|
||||
toAssistantCurrentUserError: toAssistantUserMutationError,
|
||||
toAssistantTotpEnableError,
|
||||
}),
|
||||
...createNotificationsTasksExecutors({
|
||||
@@ -2352,6 +2495,14 @@ export async function executeTool(
|
||||
if (!executor) return { content: JSON.stringify({ error: `Unknown tool: ${name}` }) };
|
||||
|
||||
try {
|
||||
const toolDefinition = TOOL_DEFINITIONS_BY_NAME.get(name);
|
||||
const accessFailure = toolDefinition
|
||||
? getAssistantToolAccessFailure(toolDefinition, ctx)
|
||||
: null;
|
||||
if (accessFailure) {
|
||||
throw toAssistantToolAccessError(accessFailure);
|
||||
}
|
||||
|
||||
const params = JSON.parse(args);
|
||||
|
||||
// Audit-log all mutation tool executions (EGAI 4.1.3.1 / IAAI 3.6.26)
|
||||
|
||||
Reference in New Issue
Block a user