import type { PrismaClient, PrincipalType } from "@prisma/client"; import { scopedFeishuPrincipalId } from "../feishu/identityNamespace.js"; export interface PrincipalRef { readonly type: PrincipalType; readonly id: string; } export interface ActorInput { readonly feishuOpenId: string; readonly chatId?: string | undefined; } export interface PrincipalResolutionScope { readonly organizationId: string; } export type PrincipalSource = | "actor-user" | "context-chat" | "team-membership" | "external-membership" | "team-external-binding"; export interface ResolvedPrincipal { readonly principal: PrincipalRef; readonly source: PrincipalSource; readonly via?: PrincipalRef | undefined; } export interface PrincipalResolution { readonly actor: ActorInput; readonly organizationId: string; readonly userId?: string | undefined; readonly principals: readonly PrincipalRef[]; readonly resolved: readonly ResolvedPrincipal[]; } export interface PrincipalResolver { resolveActor(actor: ActorInput, scope: PrincipalResolutionScope): Promise; } export class PrismaPrincipalResolver implements PrincipalResolver { constructor( private readonly prisma: PrismaClient, private readonly allowLegacyIdentity = process.env["NODE_ENV"] !== "production", ) {} async resolveActor(actor: ActorInput, scope: PrincipalResolutionScope): Promise { const resolved = new PrincipalCollector(); const scopedIdentity = await this.prisma.feishuUserIdentity.findFirst({ where: { openId: actor.feishuOpenId, connection: { organizationId: scope.organizationId, status: "ACTIVE", activeSecretVersion: { retiredAt: null }, }, }, select: { userId: true, connectionId: true }, }); // Explicit migration adapter for pre-ADR-0024 fixtures/data. Alpha Silo // ingress always has an ACTIVE connection-scoped identity. const user = scopedIdentity === null && this.allowLegacyIdentity ? await this.prisma.user.findUnique({ where: { feishuOpenId: actor.feishuOpenId }, select: { id: true }, }) : scopedIdentity === null ? null : { id: scopedIdentity.userId }; const connection = await this.prisma.organizationFeishuApplicationConnection.findUnique({ where: { organizationId: scope.organizationId }, select: { id: true, status: true, activeSecretVersion: { select: { connectionId: true, retiredAt: true } }, }, }); const activeConnectionId = scopedIdentity?.connectionId ?? ( connection?.status === "ACTIVE" && connection.activeSecretVersion !== null && connection.activeSecretVersion.connectionId === connection.id && connection.activeSecretVersion.retiredAt === null ? connection.id : undefined ); if (activeConnectionId === undefined && !this.allowLegacyIdentity) { throw new Error(`active Feishu Application Connection not found for organization ${scope.organizationId}`); } const userPrincipalId = activeConnectionId === undefined ? actor.feishuOpenId : scopedFeishuPrincipalId("USER", activeConnectionId, actor.feishuOpenId); resolved.add({ type: "USER", id: userPrincipalId }, "actor-user"); if (actor.chatId !== undefined && actor.chatId !== "") { const chatPrincipalId = activeConnectionId === undefined ? actor.chatId : scopedFeishuPrincipalId("CHAT", activeConnectionId, actor.chatId); resolved.add({ type: "FEISHU_CHAT", id: chatPrincipalId }, "context-chat"); } if (user !== null) { const memberships = await this.prisma.teamMembership.findMany({ where: { userId: user.id, revokedAt: null, team: { organizationId: scope.organizationId, archivedAt: null }, }, select: { teamId: true }, }); for (const membership of memberships) { resolved.add({ type: "TEAM", id: membership.teamId }, "team-membership"); } const externalMemberships = await this.prisma.externalPrincipalMembership.findMany({ where: { userId: user.id, revokedAt: null, connection: { organizationId: scope.organizationId, revokedAt: null, }, }, select: { principalType: true, principalId: true }, }); for (const membership of externalMemberships) { resolved.add( { type: membership.principalType, id: membership.principalId }, "external-membership", ); } } const externalPrincipals = resolved.principals().filter((principal) => isExternalPrincipal(principal.type)); if (externalPrincipals.length > 0) { const bindings = await this.prisma.teamExternalBinding.findMany({ where: { revokedAt: null, team: { organizationId: scope.organizationId, archivedAt: null }, OR: externalPrincipals.map((principal) => ({ principalType: principal.type, principalId: principal.id, })), }, select: { teamId: true, principalType: true, principalId: true }, }); for (const binding of bindings) { resolved.add( { type: "TEAM", id: binding.teamId }, "team-external-binding", { type: binding.principalType, id: binding.principalId }, ); } } return { actor, organizationId: scope.organizationId, ...(user !== null ? { userId: user.id } : {}), principals: resolved.principals(), resolved: resolved.entries(), }; } } export function principalKey(principal: PrincipalRef): string { return `${principal.type}:${principal.id}`; } function isExternalPrincipal(type: PrincipalType): boolean { return type === "FEISHU_CHAT" || type === "FEISHU_DEPARTMENT" || type === "FEISHU_USER_GROUP"; } class PrincipalCollector { private readonly refs = new Map(); private readonly provenance: ResolvedPrincipal[] = []; add(principal: PrincipalRef, source: PrincipalSource, via?: PrincipalRef): void { const key = principalKey(principal); if (!this.refs.has(key)) { this.refs.set(key, principal); } this.provenance.push({ principal, source, ...(via !== undefined ? { via } : {}), }); } principals(): readonly PrincipalRef[] { return [...this.refs.values()]; } entries(): readonly ResolvedPrincipal[] { return this.provenance; } }