---
title: "审计 Schema"
description: "AuditFields 类型、动作命名约定、主体类型、幂等性，以及该 schema 如何位于普通宽事件内部。"
canonical_url: "https://evlog.zhcndoc.com/use-cases/audit/schema"
---
# 审计 Schema

> AuditFields 类型、动作命名约定、主体类型、幂等性，以及该 schema 如何位于普通宽事件内部。

`event.audit` 是每个宽事件上的一个带类型字段。下游查询通过 `audit IS NOT NULL` 进行过滤，以便从普通日志中生成审计数据集。

## `AuditFields` 类型

<code-collapse>

```typescript
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
}
```

</code-collapse>

## 动作命名

<tip>

**action 的命名约定。** 使用 `noun.verb`（`invoice.refund`、`user.invite`、`apiKey.revoke`）。如果动作已经发生，则使用过去时（`invoice.refunded`）；如果 `withAudit()` 将解析结果，则使用现在时。将一个小型固定字典放在一个文件中，因为审计员和 SIEM 规则会查询 `action`，所以拼写错误就意味着告警缺失

</tip>

一个单独的字典文件可以让告警更容易实现：

```typescript [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
```

<tip>

如果动作数量不止几个，与其使用普通对象字典，不如使用 [`defineAuditCatalog`](https://evlog.zhcndoc.com/use-cases/audit/recording#defineauditcatalog)：同样遵循 `noun.verb` 约定，但你可以获得动作联合类型的自动补全，以及每个条目的 `target` 类型推断

</tip>

## 说明执行者

<warning>

**不要伪造主体。** 对于 cron 作业、队列 worker 和后台任务，使用 `actor.type: 'system'`；对于使用令牌认证的机器对机器调用，使用 `actor.type: 'api'`；对于 AI 工具调用，使用 `actor.type: 'agent'`。为系统操作记录一个伪造的 `'user'` 是最容易导致审计审查失败的做法。

</warning>

<table>
<thead>
  <tr>
    <th>
      <code>
        actor.type
      </code>
    </th>
    
    <th>
      适用场景
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        'user'
      </code>
    </td>
    
    <td>
      通过你的常规认证流程完成身份验证的人类用户。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'system'
      </code>
    </td>
    
    <td>
      Cron 作业、队列 worker、定时任务、内部后台进程。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'api'
      </code>
    </td>
    
    <td>
      来自另一项服务、通过令牌认证的机器对机器调用。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'agent'
      </code>
    </td>
    
    <td>
      AI 工具调用（可与 <code>
        evlog/ai
      </code>
      
       字段结合，如 <code>
        model
      </code>
      
      、<code>
        tools
      </code>
      
      、<code>
        promptId
      </code>
      
      ）。
    </td>
  </tr>
</tbody>
</table>

## 记录结果

<table>
<thead>
  <tr>
    <th>
      <code>
        outcome
      </code>
    </th>
    
    <th>
      含义
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        'success'
      </code>
    </td>
    
    <td>
      动作按要求完成。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'failure'
      </code>
    </td>
    
    <td>
      动作已尝试但失败（下游错误、竞态条件等）。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'denied'
      </code>
    </td>
    
    <td>
      动作被授权检查拒绝。
    </td>
  </tr>
</tbody>
</table>

`'failure'` 和 `'denied'` 是两种不同的情况，审计员非常关注被拒绝的动作，因为它们可能意味着探测行为或访问控制配置错误。始终记录拒绝事件（请参阅[记录事件](https://evlog.zhcndoc.com/use-cases/audit/recording#logauditdeny)）。

## 确保重试安全

`idempotencyKey` 根据 `action`、`actor.id`、`target` 和粗粒度时间戳的哈希自动派生。结果是，即使你的 drain 在网络短暂中断后重试审计插入，重复行也会在 `ON CONFLICT DO NOTHING` 时合并。你不必考虑这一点，因为该字段会自动为你填充。

将该字段用作 Postgres / Bigtable / DynamoDB 中的主键，这样重试就能从设计上保持安全。

## 将动作与其原因关联

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      使用场景
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        correlationId
      </code>
    </td>
    
    <td>
      由属于<strong>
        同一操作
      </strong>
      
      的每个审计事件共享（例如：一次 HTTP 请求触发退款 + 邮件 + webhook）。
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        causationId
      </code>
    </td>
    
    <td>
      导致此事件的前一个审计事件的 ID。适用于重建级联动作链条。
    </td>
  </tr>
</tbody>
</table>

大多数团队会将 `correlationId` 设置为 `requestId`。`causationId` 是可选的，只有在单个用户动作触发许多内部审计事件时才值得填写。

---

- [概览](https://evlog.zhcndoc.com/use-cases/audit/overview)
- [记录](https://evlog.zhcndoc.com/use-cases/audit/recording)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
