/** * Permission compatibility exports. * * ADR-0019 moves production authorization behind PrincipalResolver + * PermissionAuthorizer. The wrappers below preserve the old helper names for * tests and incremental callers while routing project trigger checks through * the new authorizer. */ import type { PrismaClient } from "@prisma/client"; import { createPermissionAuthorizer } from "./permissions/authorizer.js"; export { createPermissionAuthorizer, PrismaPermissionAuthorizer, roleGrantsEdit } from "./permissions/authorizer.js"; export { PrismaPrincipalResolver, principalKey } from "./permissions/principals.js"; export { syncExternalPrincipalMemberships } from "./permissions/externalSync.js"; export { grantTeamProjectAccess, listProjectTeamAccess, revokeTeamProjectAccess } from "./permissions/projectTeamAccess.js"; export type { AuthorizationAction, AuthorizationDecision, AuthorizationRequest, AuthorizationResource, PermissionAuthorizer, } from "./permissions/authorizer.js"; export type { ActorInput, PrincipalRef, PrincipalResolution, PrincipalResolver, ResolvedPrincipal, } from "./permissions/principals.js"; export type { ExternalPrincipalMembershipInput, ExternalPrincipalType, SyncExternalPrincipalMembershipsInput, SyncExternalPrincipalMembershipsResult, } from "./permissions/externalSync.js"; export type { GrantTeamProjectAccessInput, ProjectTeamAccessEntry, RevokeTeamProjectAccessInput, } from "./permissions/projectTeamAccess.js"; export interface PermissionResult { readonly allowed: boolean; readonly reason: string; } export async function canTriggerAgent( prisma: PrismaClient, projectId: string, principal: string, ): Promise { try { const decision = await createPermissionAuthorizer(prisma).can({ actor: { feishuOpenId: principal }, action: "agent.trigger", resource: { type: "PROJECT", id: projectId }, }); return { allowed: decision.allowed, reason: decision.reason }; } catch (e) { return { allowed: false, reason: `permission check failed: ${e instanceof Error ? e.message : String(e)}` }; } } /** * Role-only legacy helper. New trigger code uses `role.trigger` on * PermissionAuthorizer so project edit+ and role grant composition happen in * one decision. */ export async function canTriggerRole( prisma: PrismaClient, projectId: string, roleId: string, principal: string, ): Promise { try { const anyGrant = await prisma.roleTriggerGrant.findFirst({ where: { projectId, roleId }, select: { id: true }, }); if (anyGrant === null) { return { allowed: true, reason: `role ${roleId} unconfigured on project (open)` }; } const grant = await prisma.roleTriggerGrant.findFirst({ where: { projectId, roleId, principalType: "USER", principalId: principal, revokedAt: null }, select: { id: true }, }); if (grant !== null) { return { allowed: true, reason: `granted role ${roleId}` }; } return { allowed: false, reason: `no active ${roleId} grant for ${principal} on project ${projectId}`, }; } catch (e) { return { allowed: false, reason: `role permission check failed: ${e instanceof Error ? e.message : String(e)}` }; } }