forked from bai/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:
@@ -0,0 +1,23 @@
|
||||
-- FeishuEventReceipt: idempotency for inbound ws events (at-least-once redelivery).
|
||||
CREATE TABLE "FeishuEventReceipt" (
|
||||
"id" TEXT NOT NULL,
|
||||
"eventId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"messageId" TEXT,
|
||||
"receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FeishuEventReceipt_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "FeishuEventReceipt_eventId_key" ON "FeishuEventReceipt"("eventId");
|
||||
CREATE INDEX "FeishuEventReceipt_eventType_idx" ON "FeishuEventReceipt"("eventType");
|
||||
CREATE INDEX "FeishuEventReceipt_messageId_idx" ON "FeishuEventReceipt"("messageId");
|
||||
CREATE INDEX "FeishuEventReceipt_receivedAt_idx" ON "FeishuEventReceipt"("receivedAt");
|
||||
|
||||
-- AuditEntry: add projectId so audit points before/without a run (permission
|
||||
-- denials, slash commands) are locatable to a project.
|
||||
ALTER TABLE "AuditEntry" ADD COLUMN "projectId" TEXT;
|
||||
|
||||
CREATE INDEX "AuditEntry_projectId_createdAt_idx" ON "AuditEntry"("projectId", "createdAt");
|
||||
|
||||
ALTER TABLE "AuditEntry" ADD CONSTRAINT "AuditEntry_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -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