event.audit 是每个宽事件上的一个带类型字段。下游查询通过 audit IS NOT NULL 进行过滤,以便从普通日志中生成审计数据集。
AuditFields 类型
interface AuditFields {
action: string // 'invoice.refund'
actor: {
type: 'user' | 'system' | 'api' | 'agent'
id: string
displayName?: string
email?: string
// 对于 type === 'agent',镜像 evlog/ai 字段:
model?: string
tools?: string[]
reason?: string
promptId?: string
}
target?: { type: string, id: string, [k: string]: unknown }
outcome: 'success' | 'failure' | 'denied'
reason?: string
changes?: { before?: unknown, after?: unknown, patch?: AuditPatchOp[] }
causationId?: string // 导致此动作的动作 ID
correlationId?: string // 在一次操作中的每个动作共享
version?: number // 默认为 1
idempotencyKey?: string // 自动派生;在 drain 重试中保持安全
context?: { // 由 auditEnricher 填充
requestId?: string
traceId?: string
ip?: string
userAgent?: string
tenantId?: string
}
signature?: string // 由 signed({ strategy: 'hmac' }) 设置
prevHash?: string // 由 signed({ strategy: 'hash-chain' }) 设置
hash?: string
}
动作命名
action 的命名约定。 使用 noun.verb(invoice.refund、user.invite、apiKey.revoke)。如果动作已经发生,则使用过去时(invoice.refunded);如果 withAudit() 将解析结果,则使用现在时。将小型固定字典保存在一个文件中——审计员和 SIEM 规则会根据 action 进行查询,因此一个拼写错误就意味着漏报告警。一个单独的字典文件可以让告警更容易实现:
src/audit/actions.ts
export const AUDIT_ACTIONS = {
USER_INVITE: 'user.invite',
USER_REMOVE: 'user.remove',
USER_ROLE_CHANGE: 'user.role-change',
INVOICE_REFUND: 'invoice.refund',
API_KEY_REVOKE: 'apiKey.revoke',
} as const
对于超过少数几个动作的情况,建议使用
defineAuditCatalog 而不是普通对象字典——同样遵循 noun.verb 约定,但你还能免费获得动作联合类型的自动补全以及每个条目的 target 类型推断。主体类型
不要伪造主体。 对于 cron 作业、队列 worker 和后台任务,使用
actor.type: 'system';对于使用令牌认证的机器对机器调用,使用 actor.type: 'api';对于 AI 工具调用,使用 actor.type: 'agent'。为系统操作记录一个伪造的 'user' 是最容易导致审计审查失败的做法。actor.type | 适用场景 |
|---|---|
'user' | 通过你的常规认证流程完成身份验证的人类用户。 |
'system' | Cron 作业、队列 worker、定时任务、内部后台进程。 |
'api' | 来自另一项服务、通过令牌认证的机器对机器调用。 |
'agent' | AI 工具调用(可与 evlog/ai 字段结合,如 model、tools、promptId)。 |
结果
outcome | 含义 |
|---|---|
'success' | 动作按要求完成。 |
'failure' | 动作已尝试但失败(下游错误、竞态条件等)。 |
'denied' | 动作被授权检查拒绝。 |
'failure' 和 'denied' 是不同的概念——审计员非常关注被拒绝的动作,因为它们通常意味着探测行为或访问控制配置错误。务必记录拒绝事件(参见 Recording Events)。
幂等性
idempotencyKey 由 action、actor.id、target 和一个粗粒度时间戳的哈希自动派生。结果是:即使你的 drain 因网络抖动而重试审计插入,重复行也会在 ON CONFLICT DO NOTHING 下被折叠。你无需为此担心——它已经为你填好了。
将该字段用作 Postgres / Bigtable / DynamoDB 中的主键,这样重试就能从设计上保持安全。
因果与关联
| 字段 | 使用场景 |
|---|---|
correlationId | 由属于同一操作的每个审计事件共享(例如:一次 HTTP 请求触发退款 + 邮件 + webhook)。 |
causationId | 导致此事件的前一个审计事件的 ID。适用于重建级联动作链条。 |
大多数团队会将 correlationId 设置为 requestId。causationId 是可选的,只有在单个用户动作触发许多内部审计事件时才值得填写。