import { User } from "./User"; /** * Workspaces bundle interfaces (client subset). Mirrors the server's * `IWorkspace` / `IWorkspaceMember` / `IWorkspaceInvitation` shapes and the * workspace controller responses. * * `capabilities` is a CLOSED, Sublay-enforced vocabulary (cascades via reach); * `permissions` is opaque free-form developer strings Sublay never consumes * (per-node only, does NOT cascade). */ export type WorkspaceCapability = "view" | "invite" | "remove-member" | "edit-member-access" | "edit-member-profile" | "create-sub-workspace" | "edit-workspace"; export type WorkspaceInvitationStatus = "pending" | "accepted" | "declined" | "revoked"; export type WorkspaceAuthorityReason = "owner" | "ancestor-owner" | "member" | "reach-holder"; export type WorkspaceAuthorityReasonDetail = { type: "owner" | "member"; viaWorkspaceId?: never; } | { type: "ancestor-owner" | "reach-holder"; viaWorkspaceId: string; }; export type WorkspaceInclude = "memberCount"; export type WorkspaceIncludeArray = WorkspaceInclude[]; export type WorkspaceIncludeParam = string | WorkspaceIncludeArray; export interface Workspace { id: string; name: string; metadata: Record; ownerId: string; parentWorkspaceId: string | null; depth: number; inheritsFromParent: boolean; createdAt: string; updatedAt: string; memberCount?: number; } export interface WorkspaceMember { id: string; workspaceId: string; userId: string; capabilities: WorkspaceCapability[]; permissions: string[]; rank: number; title: string | null; metadata: Record; joinedAt: string; createdAt: string; } export interface WorkspaceInvitation { id: string; workspaceId: string; invitedBy: string; userId: string | null; email: string | null; capabilities: WorkspaceCapability[]; permissions: string[]; rank: number; title: string | null; status: WorkspaceInvitationStatus; expiresAt: string; createdAt: string; updatedAt: string; } export interface WorkspaceRosterReason { type: "owner" | "member" | "ancestor-owner" | "reach-holder" | "descendant-member"; rank?: number; capabilities?: WorkspaceCapability[]; permissions?: string[]; title?: string | null; metadata?: Record; viaWorkspaceId?: string; workspaceId?: string; } export interface WorkspaceRosterEntry { user: User; reasons: WorkspaceRosterReason[]; } export interface WorkspaceRosterResponse { data: WorkspaceRosterEntry[]; total: number; } export interface WorkspaceRosterCountsResponse { counts: { owner: number; member: number; ancestorOwner: number; reachHolder: number; descendantMember: number; }; total: number; distinctUsers: number; } /** * The `user` carried by a standing read. Normally the full user record, but the * server falls back to `{ id }` alone when the user row is gone (a deleted user * with a lingering membership row is a reachable case), so every field except * `id` may be absent. */ export type WorkspaceStandingUser = Pick & Partial>; export interface WorkspaceMemberStanding { user: WorkspaceStandingUser; reasons: WorkspaceAuthorityReasonDetail[]; capabilities?: WorkspaceCapability[]; permissions?: string[]; rank?: number | null; title: string | null; metadata: Record; } export interface WorkspaceAuthority { reasons: WorkspaceAuthorityReasonDetail[]; capabilities: WorkspaceCapability[]; permissions: string[]; rank: number | null; }