feat(filelib): 操作日志模块——防篡改哈希链、组合查询与 CSV 导出

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 22:54:45 +08:00
parent c96ea60482
commit 26523d1b54
29 changed files with 2881 additions and 209 deletions
+84
View File
@@ -52,6 +52,7 @@ model Organization {
auditEntries AuditEntry[] @relation("organizationAudit")
projectSearchDocuments ProjectSearchDocument[]
fileLibNodes FileLibNode[]
fileLibAuditLogs FileLibAuditLog[] @relation("organizationFileLibAudit")
@@index([status])
}
@@ -1140,3 +1141,86 @@ model FileLibExportJob {
@@index([organizationId, status])
}
// --- 文件库审计日志(横切模块) -------------------------------------------
/// 审计对象类型。objectId 故意不建 FK:审计是不可变历史事实,对象被彻底
/// 删除(node.purge)后日志仍须完整可读,不能被级联带走。
enum FileLibAuditObjectType {
FOLDER
PROJECT
FILE
GRANT
EXPORT_JOB
GROUP
SYSTEM
}
enum FileLibAuditResult {
SUCCESS
FAILURE
}
/// 文件库审计日志(ADR-0039)。与既有 `AuditEntry`(Project/Run 口径、
/// best-effort)及 Platform Audit(ADR-0023)三者分离:本表是文件版本管理与
/// 权限管理子模块的合规证据,字段结构化、只追加、带哈希链防篡改。
///
/// 不可篡改:服务路径只 INSERT/SELECT。迁移 SQL 里装了 BEFORE UPDATE OR
/// DELETE 触发器,任何改写/删除一律抛异常 —— 应用层写错也拦得住。保留策略
/// 归档(≥180 天)是唯一合法的出场方式,走 archivedAt 打标而非物理删除。
///
/// 哈希链:每条 entryHash = sha256(规范化载荷 + prevHash),按 org 串成单链,
/// seq 单调递增。整链可离线校验(见 auditVerify),断链即篡改或漏写。
model FileLibAuditLog {
id String @id @default(cuid())
organizationId String
/// org 内单调递增序号,哈希链的顺序权威(createdAt 同秒不可排序)。
seq BigInt
/// 操作时间。DB 侧 timestamptz,读出即带时区。
occurredAt DateTime @default(now())
action String
result FileLibAuditResult @default(SUCCESS)
/// 失败原因(result=FAILURE 时的错误码 + 消息);成功为 null。
failureReason String?
/// 操作人 id。故意不建 FK:用户注销后审计仍须留痕。
actorUserId String
/// 操作人姓名快照 —— 记录当时的显示名,不随用户改名而变。
actorName String
/// 操作时是否以网站管理员身份行事(高危操作甄别)。
actorIsAdmin Boolean @default(false)
objectType FileLibAuditObjectType
objectId String
/// 对象名称;节点为 name,文件为文件名,组为组名。
objectName String
/// 对象路径:节点 pathIds,文件为 "<pathIds>:<filePath>"。前缀匹配可查子树。
objectPath String
/// 操作前值(结构化)。创建类操作为 null。
beforeValue Json?
/// 操作后值(结构化)。删除类操作为 null。
afterValue Json?
/// 附加上下文:冲突基线版本、导出参数、commit message 等。
context Json?
clientIp String?
userAgent String?
/// 本条的链哈希;prevHash 为 org 内上一条的 entryHash(首条为 null)。
entryHash String
prevHash String?
/// 保留策略归档时间(≥180 天后打标)。非 null 即已归档,默认查询不返回。
archivedAt DateTime?
organization Organization @relation("organizationFileLibAudit", fields: [organizationId], references: [id], onDelete: Cascade)
@@unique([organizationId, seq])
@@index([organizationId, occurredAt])
@@index([organizationId, actorUserId, occurredAt])
@@index([organizationId, action, occurredAt])
@@index([organizationId, objectType, objectId])
@@index([organizationId, objectPath])
@@index([organizationId, archivedAt])
}