feat: add org admin project onboarding foundation

This commit is contained in:
2026-07-10 00:25:00 +08:00
parent 2b7aa3294f
commit 34e07e229f
16 changed files with 1072 additions and 38 deletions
+55 -17
View File
@@ -36,6 +36,8 @@ model Organization {
updatedAt DateTime @updatedAt
memberships OrganizationMembership[]
projectSettings OrganizationProjectSettings?
folders Folder[]
projects Project[]
teams Team[]
externalDirectoryConnections ExternalDirectoryConnection[]
@@ -73,6 +75,17 @@ enum OrganizationMemberRole {
MEMBER
}
/// ADR-0021: org-level project onboarding policy. Ordinary Feishu users can
/// create projects from unbound chats only when membersCanCreateProjects=true.
model OrganizationProjectSettings {
organizationId String @id
membersCanCreateProjects Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
}
/// A Feishu user known to the Hub. principal sub-typology is OPEN (ADR-0004).
model User {
id String @id @default(cuid())
@@ -222,9 +235,32 @@ model ExternalPrincipalMembership {
// --- Project & Feishu binding (ADR-0001) ---------------------------------
/// ADR-0021: transparent project explorer folder. Folders are org-scoped
/// navigation/aggregation nodes, not permission resources; project grants stay
/// attached to PROJECT resources.
model Folder {
id String @id @default(cuid())
organizationId String
parentId String?
name String
sortKey String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
archivedAt DateTime?
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
parent Folder? @relation("folderTree", fields: [parentId], references: [id], onDelete: Restrict)
children Folder[] @relation("folderTree")
projects Project[]
@@index([organizationId, parentId, archivedAt])
@@index([organizationId, parentId, sortKey])
}
model Project {
id String @id @default(cuid())
organizationId String
folderId String?
name String
workspaceDir String
createdByUserId String?
@@ -232,37 +268,39 @@ model Project {
updatedAt DateTime @updatedAt
archivedAt DateTime?
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
createdBy User? @relation("projectCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
groupBinding ProjectGroupBinding?
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
createdBy User? @relation("projectCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
groupBindings ProjectGroupBinding[]
agentSessions AgentSession[]
agentRuns AgentRun[]
agentLock ProjectAgentLock?
roleTriggerGrants RoleTriggerGrant[] @relation("projectRoleGrants")
auditEntries AuditEntry[] @relation("projectAudit")
fileChanges AgentFileChange[] @relation("projectFileChanges")
roleTriggerGrants RoleTriggerGrant[] @relation("projectRoleGrants")
auditEntries AuditEntry[] @relation("projectAudit")
fileChanges AgentFileChange[] @relation("projectFileChanges")
@@index([organizationId, archivedAt])
@@index([folderId, archivedAt])
@@index([archivedAt])
}
/// ADR-0001: one project ↔ one Feishu chat (1:1). `chatId` is unique ⇒
/// GroupBinding.WellFormed's injectivity half (no two projects bind one chat).
/// The "at most one binding per project" half is enforced by the 1:1 relation.
/// Group dissolution lifecycle is OPEN (ADR-0001 Consequences) — not modeled
/// here; archival is a future policy, not a current column.
/// ADR-0001 + ADR-0021: active bindings are one project ↔ one Feishu chat
/// (1:1). Historical archived bindings are retained for audit; partial unique
/// indexes in migrations enforce one active binding per project and per chat.
model ProjectGroupBinding {
id String @id @default(cuid())
projectId String @unique
chatId String @unique
id String @id @default(cuid())
projectId String
chatId String
createdByUserId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
archivedAt DateTime?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdBy User? @relation("bindingCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
@@index([chatId])
@@index([projectId, archivedAt])
@@index([chatId, archivedAt])
}
// --- AgentRun, session, lock (ADR-0002, 0017) -----------------------------