forked from EduCraft/curriculum-project-hub
feat(hub): 事件去重(A-4) + 审计写入路径(A-8)
A-4 FeishuEventReceipt: - 新增 FeishuEventReceipt 表(eventId @unique),lark ws 至少一次投递的幂等兜底 - MessageReceiveEvent 加 header.event_id 字段 - trigger 入口查重:已存在的 event_id → 跳过(不建第二次 run) - resetDb 加入 FeishuEventReceipt A-8 审计写入: - AuditEntry 加 projectId(权限拒绝/slash 命令等无 run 的审计点可定位) - 新建 src/audit.ts:writeAudit(prisma, entry) best-effort 写入(吞错,不阻断 trigger) - trigger 五个审计点:trigger.denied / trigger.role_denied / run.created / run.lock_race / run.finished / run.failed - 新增 audit unit 测试(3) + trigger 集成测试(去重 + 审计,2) 59 tests 全过。
This commit is contained in:
@@ -83,6 +83,7 @@ model Project {
|
||||
permissionGrants PermissionGrant[] @relation("projectGrants")
|
||||
permissionSettings PermissionSettings[] @relation("projectSettings")
|
||||
roleTriggerGrants RoleTriggerGrant[] @relation("projectRoleGrants")
|
||||
auditEntries AuditEntry[] @relation("projectAudit")
|
||||
|
||||
@@index([archivedAt])
|
||||
}
|
||||
@@ -294,18 +295,39 @@ model RoleTriggerGrant {
|
||||
|
||||
/// spec AuditEntry: minimal skeleton — one entry relates to a run. Event type,
|
||||
/// actor, timestamp, details are OPEN. This table mirrors that: `runId` is the
|
||||
/// PINNED relation; the rest is open-shaped columns.
|
||||
model AuditEntry {
|
||||
id String @id @default(cuid())
|
||||
runId String?
|
||||
projectId String?
|
||||
actorUserId String?
|
||||
action String
|
||||
metadata Json
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
actor User? @relation("auditActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
||||
actor User? @relation("auditActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
||||
project Project? @relation("projectAudit", fields: [projectId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([runId])
|
||||
@@index([projectId, createdAt])
|
||||
@@index([actorUserId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
// --- Feishu event dedup --------------------------------------------------
|
||||
|
||||
/// Idempotency receipt for inbound Feishu ws events. The lark ws client may
|
||||
/// redeliver an event (at-least-once); without dedup a duplicate would spawn a
|
||||
/// second AgentRun — the lock would refuse it, but a FAILED run row + a busy
|
||||
/// reply would still leak. `eventId @unique` makes the second insert a no-op
|
||||
/// signal: the handler checks existence before processing.
|
||||
model FeishuEventReceipt {
|
||||
id String @id @default(cuid())
|
||||
eventId String @unique
|
||||
eventType String
|
||||
messageId String?
|
||||
receivedAt DateTime @default(now())
|
||||
|
||||
@@index([eventType])
|
||||
@@index([messageId])
|
||||
@@index([receivedAt])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user