@h-ai/audit 0.1.0-alpha9 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +199 -0
- package/README.ja.md +199 -0
- package/README.md +108 -22
- package/dist/index.d.ts +359 -76
- package/dist/index.js +1163 -144
- package/dist/index.js.map +1 -1
- package/package.json +35 -10
package/README.en.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @h-ai/audit
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md) · [@h-ai/reldb](../reldb/README.en.md)
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
A unified audit logging module. The `audit` object records, queries, cleans up, and aggregates audit logs, with persistence provided by `@h-ai/reldb`.
|
|
10
|
+
|
|
11
|
+
SQLite, PostgreSQL and MySQL share the same read contract: `createdAt` is a `Date`, `details` is a JSON string or `null`, and absent nullable fields return `null`. Date filters use the same driver conversion as writes. Counts are safe integers; out-of-range counts return `STATS_FAILED`.
|
|
12
|
+
|
|
13
|
+
## Core features
|
|
14
|
+
|
|
15
|
+
- Record user actions and security events; filter by user, date and action.
|
|
16
|
+
- Query paginated logs and statistics, and clean up by retention period.
|
|
17
|
+
- Persist through the relational database and redact credential fields before writing.
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
### Dependencies
|
|
22
|
+
|
|
23
|
+
- `@h-ai/reldb`: database for audit log persistence; **initialize it before audit.init()**.
|
|
24
|
+
|
|
25
|
+
### Use cases
|
|
26
|
+
|
|
27
|
+
- User activity auditing (login, logout, registration, password reset, etc.)
|
|
28
|
+
- CRUD auditing (resource creation, reads, updates, and deletion)
|
|
29
|
+
- Security event tracking and compliance auditing
|
|
30
|
+
- Operational statistics (operation frequency, active users, etc.)
|
|
31
|
+
|
|
32
|
+
### Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { audit } from '@h-ai/audit'
|
|
36
|
+
import { reldb } from '@h-ai/reldb'
|
|
37
|
+
|
|
38
|
+
// 1. 初始化依赖
|
|
39
|
+
await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
40
|
+
|
|
41
|
+
// 2. 初始化审计模块(自动使用已初始化的 reldb 单例)
|
|
42
|
+
const result = await audit.init()
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
throw new Error(result.error.message)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. 记录审计日志
|
|
48
|
+
await audit.log({
|
|
49
|
+
userId: 'user_1',
|
|
50
|
+
action: 'login',
|
|
51
|
+
resource: 'auth',
|
|
52
|
+
ipAddress: '127.0.0.1',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 4. 使用便捷方法
|
|
56
|
+
await audit.helper.login('user_1', '127.0.0.1')
|
|
57
|
+
await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2', details: { name: '张三' } })
|
|
58
|
+
|
|
59
|
+
// 5. 查询日志
|
|
60
|
+
const logs = await audit.list({ pageSize: 20, action: 'login' })
|
|
61
|
+
|
|
62
|
+
// 6. 获取统计
|
|
63
|
+
const stats = await audit.getStats(7) // 最近 7 天
|
|
64
|
+
|
|
65
|
+
// 7. 清理旧日志
|
|
66
|
+
await audit.cleanup(90) // 清理 90 天前的日志
|
|
67
|
+
|
|
68
|
+
// 8. 关闭
|
|
69
|
+
const closeResult = await audit.close()
|
|
70
|
+
if (!closeResult.success) {
|
|
71
|
+
throw new Error(closeResult.error.message)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Initialization options
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// 默认配置(无需传参)
|
|
79
|
+
await audit.init()
|
|
80
|
+
|
|
81
|
+
// 自定义用户表映射
|
|
82
|
+
await audit.init({
|
|
83
|
+
userTable: 'hai_iam_users', // 用户表名(默认 'hai_iam_users')
|
|
84
|
+
userIdColumn: 'id', // 用户表主键列名(默认 'id')
|
|
85
|
+
userNameColumn: 'username', // 用户表用户名列名(默认 'username')
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Audit logs are stored in the fixed `hai_audit_logs` table and require an initialized `@h-ai/reldb`.
|
|
90
|
+
|
|
91
|
+
### API overview
|
|
92
|
+
|
|
93
|
+
- `audit.init(config)`: initialize the audit module.
|
|
94
|
+
- `audit.log(input)`: record an audit log.
|
|
95
|
+
- `audit.list(options)`: returns `PaginatedResult<AuditLogWithUser>` (items/total/page/pageSize, including usernames). Core normalizes pagination, with pageSize 20 by default; consume the returned metadata directly.
|
|
96
|
+
- `audit.getUserRecent(userId, limit)`: get recent user activity.
|
|
97
|
+
- `audit.cleanup(days)`: delete old logs.
|
|
98
|
+
- `audit.getStats(days)`: get operation statistics.
|
|
99
|
+
- `audit.helper.login/logout/register/...`: convenient log helpers.
|
|
100
|
+
- `audit.close()`: close the module.
|
|
101
|
+
|
|
102
|
+
Record common events through `audit.helper`, with the public `AuditHelper` type. The helper's internal assembly factory is outside the package API.
|
|
103
|
+
|
|
104
|
+
#### Input constraints
|
|
105
|
+
|
|
106
|
+
- `audit.log(input)`: `action` / `resource` must be nonempty strings of at most 256 characters.
|
|
107
|
+
- `audit.log(input)`: credential fields in `details` (such as `password` / `token` / `secret`) are redacted automatically before persistence; business privacy fields such as email and phone are also hidden by default and can be configured through privacy.
|
|
108
|
+
- `audit.list(options)`: when both `startDate` and `endDate` are provided, `startDate <= endDate` is required.
|
|
109
|
+
- `audit.getUserRecent(userId, limit)`: `userId` must be nonempty; `limit`, if provided, must be a positive integer.
|
|
110
|
+
- `audit.cleanup(days)`: `days`, if provided, must be a nonnegative integer.
|
|
111
|
+
- `audit.getStats(days)`: `days`, if provided, must be a nonnegative integer.
|
|
112
|
+
|
|
113
|
+
### Error codes
|
|
114
|
+
|
|
115
|
+
Business data operations return `HaiResult<T>`. Common error codes:
|
|
116
|
+
|
|
117
|
+
| Error code | code | Description |
|
|
118
|
+
| -------------------------------- | --------------- | ------------------------------------ |
|
|
119
|
+
| `HaiAuditError.LOG_FAILED` | `hai:audit:001` | Recording failed |
|
|
120
|
+
| `HaiAuditError.QUERY_FAILED` | `hai:audit:002` | Query failed |
|
|
121
|
+
| `HaiAuditError.CLEANUP_FAILED` | `hai:audit:003` | Cleanup failed |
|
|
122
|
+
| `HaiAuditError.STATS_FAILED` | `hai:audit:004` | Aggregation failed |
|
|
123
|
+
| `HaiAuditError.INIT_IN_PROGRESS` | `hai:audit:005` | Initialization in progress |
|
|
124
|
+
| `HaiAuditError.NOT_INITIALIZED` | `hai:audit:010` | Module not initialized |
|
|
125
|
+
| `HaiAuditError.CONFIG_ERROR` | `hai:audit:012` | Invalid configuration |
|
|
126
|
+
| `HaiAuditError.BACKUP_DISABLED` | `hai:audit:020` | Backup disabled |
|
|
127
|
+
| `HaiAuditError.BACKUP_FAILED` | `hai:audit:021` | Backup or verification failed |
|
|
128
|
+
| `HaiAuditError.BACKUP_INVALID` | `hai:audit:022` | Invalid backup input |
|
|
129
|
+
| `HaiAuditError.BACKUP_CONFLICT` | `hai:audit:023` | Backup ID/export conditions conflict |
|
|
130
|
+
| `HaiAuditError.ALERTS_DISABLED` | `hai:audit:024` | Alerts disabled |
|
|
131
|
+
| `HaiAuditError.ALERTS_FAILED` | `hai:audit:025` | Alert check or notification failed |
|
|
132
|
+
|
|
133
|
+
> Note: `audit.close()` also returns `HaiResult<void>`. Email details in `audit.helper.passwordResetRequest()` are hidden by the default business privacy settings.
|
|
134
|
+
|
|
135
|
+
`audit.init()` awaits automatic table creation and repository readiness before success. DDL failures leave `isInitialized=false`; correct the database and retry. Custom joins, statistics and cleanup share the same barrier. Closing during initialization invalidates late publication. Check the init HaiResult before recording or querying. Audit does not own the shared RelDB connection; close audit before the database.
|
|
136
|
+
|
|
137
|
+
## Event context, privacy and retention
|
|
138
|
+
|
|
139
|
+
Audit createdAt, alert windows and archive cutoffs share the database clock rather than individual node clocks. Scheduled backup/alert tasks declare idempotency based on their persistent deduplication guarantees; failures do not incorrectly block subsequent schedules as non-idempotent unknown tasks.
|
|
140
|
+
|
|
141
|
+
`capture: { enabled: true, includeReads: false }` controls automatic framework events. IAM records important account, authentication, password, authorization, API Key and session changes. Serv/Kit bind verified identity and trusted client addresses and capture writes and failures/denials; successful reads are skipped by default. Protocol or validation failures before identity is established remain anonymous. HTTP events contain operation/method/status or reason codes, without request bodies, tokens or error details. Applications still call `audit.log()` at domain completion/failure boundaries; disabling capture does not disable explicit logs. Initialize audit before IAM and close it after IAM. Trusted jobs/CLI callers must supply their actor and correlation context.
|
|
142
|
+
|
|
143
|
+
`audit.log(input, tx)` and `audit.capture(input, { tx })` use an existing RelDB transaction: events commit or roll back with the change, without an independent commit or a false success record after rollback. Record failed/denied attempts at the caller boundary after rollback. Automatic audit failures emit correlated diagnostics and preserve an already-applied business result to avoid unsafe retries. When recording is mandatory, check an explicit log's HaiResult in the same transaction before committing.
|
|
144
|
+
|
|
145
|
+
New records include category (system/business), outcome (success/failure/denied), severity (info/warning/error/critical), request/trace/scope IDs and actorSnapshot. Identity is copied at event time and survives account renames, role changes and deletion. With userId alone, configured name lookup captures the available username; lookup failure retains an explicitly unnamed identity and emits diagnostics. Trusted HTTP hosts should provide `audit.withContext({ actor, requestId, ipAddress, userAgent }, operation)` using verified identities. This context tool preserves the operation's return value or exception; business writes return HaiResult.
|
|
146
|
+
|
|
147
|
+
`audit.list({ category, outcome, severity, scopeId, requestId, traceId, sortBy, sortDirection })` filters and sorts through controlled fields. `getStats(7, { category: 'system' })` counts operations in that category. Business privacy is enabled by default; `privacy.fields` adds project field names. Credentials are always hidden, even with business privacy disabled. Hide the whole configured field when free text contains private information.
|
|
148
|
+
|
|
149
|
+
`retention: { enabled: true, minDays: 90 }` activates a shared database policy. Cleanup honors the actual minimum even with cleanup(0); triggers reject protected UPDATE, DELETE and conflicting replacement writes. An unconfigured fresh framework installation does not force retention protection: the default cleanup argument of 90 days alone is no immutability guarantee. Reinitialization without retention preserves the database policy; explicit trusted startup configuration can disable it. This covers ordinary row writes; database-owner DDL/privileges and stronger archive/storage protection belong to deployment management.
|
|
150
|
+
|
|
151
|
+
Stop old writers and back up before upgrading. `migrateAudit({ dryRun: true })` previews without writes; audit.init applies repeatable migrations. Unknown historical fields remain null. Legacy joined usernames are display compatibility, rather than historical identity evidence. Failed writes return failure and emit correlated diagnostics. close drains started writes before RelDB can close.
|
|
152
|
+
|
|
153
|
+
Redaction matches whole fields, including objects, arrays and sensitive values produced by custom JSON projections. capture returns not-initialized failure before initialization or after close; only an initialized module with capture disabled skips automatic events.
|
|
154
|
+
|
|
155
|
+
## Private backups and scheduled archives
|
|
156
|
+
|
|
157
|
+
`backup.enabled` is off by default. Inject initialized `StorageFunctions` with `audit.init({ storage, backup: { enabled: true } })`; initialize RelDB first. `backup.batchSize` controls scan batches (default200), while `maxRecordBytes` bounds each UTF-8 record (default1 MiB). Oversized records fail the archive without truncating stored evidence.
|
|
158
|
+
|
|
159
|
+
`audit.backup.run({ id?, from?, to?, signal? })` writes private JSONL and returns its ID/key, count, SHA-256 and start/cutoff dates. The fixed namespace is `AUDIT_BACKUP_PREFIX` (hai-private/audit-backups/). Use a stable ID for one execution: a complete existing archive is verified and reused. Different export conditions, incomplete or damaged files fail rather than overwrite. Conditional-write races and uncertain writes are reconciled against the actual object. `audit.backup.verify(key, { signal? })` streams bounded format/count/checksum verification. The checksum establishes integrity, not authenticity.
|
|
160
|
+
|
|
161
|
+
Version1 contains a header, `{type:'entry',record}` lines and a footer. Entries preserve original IDs, ISO dates, stored details and nullable historical identities without joining current usernames. The footer SHA-256 covers all preceding UTF-8 lines including newlines; recordCount counts entry lines. Verified archives can be read offline or restored into a separate query store, without replacing protected live records. Online export uses a fixed cutoff and primary-key cursor; coverage reflects the actual scan. Pause writes/cleanup or use a native database snapshot for recovery at one exact point in time.
|
|
162
|
+
|
|
163
|
+
`backup.schedule` configures enabled, five-field cron (default `0 2 * * *`) and IANA timezone (default UTC). When both switches are enabled, `audit.schedulerPlan` returns tasks/hooks for explicit composition into `scheduler.init()`; it is null when no automatic backup or alert tasks are enabled. Its callback uses Scheduler's shared occurrence ID and cancellation signal and throws on failure so the task fails correctly. Reconcile unknown executions using Scheduler's rules. The example/fullstack runtimes wire this plan; other hosts explicitly compose existing tasks/hooks. Close Scheduler, IAM, Audit, Storage and RelDB in that order. Audit cancels/drains its own backups and leaves host Storage ownership intact.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const plan = audit.schedulerPlan
|
|
167
|
+
if (plan) {
|
|
168
|
+
const initialized = await scheduler.init({ ...schedulerSettings, ...plan })
|
|
169
|
+
if (!initialized.success)
|
|
170
|
+
throw new Error(initialized.error.message)
|
|
171
|
+
const started = scheduler.start()
|
|
172
|
+
if (!started.success)
|
|
173
|
+
throw new Error(started.error.message)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The host imports audit/scheduler and supplies validated schedulerSettings, with Audit/Storage/RelDB initialized. Use private archive storage: enabling backups rejects an S3 publicUrl setting, and deployment must restrict bucket/CDN access. Public asset endpoints should expose designated resources only. The example/fullstack runtimes expose canonical avatar paths; archives require an authorized audit download endpoint. Disabling backups preserves the private boundary of existing files.
|
|
178
|
+
|
|
179
|
+
## Graded anomaly alerts
|
|
180
|
+
|
|
181
|
+
`alerts.enabled` defaults to false. Inject initialized Reach when enabling it: `audit.init({ reach, alerts: { enabled: true, rules: [...] } })`. Each rule has a stable id, exact actions, optional resource, outcomes and minimumSeverity filters, threshold/windowSeconds, notification severity, and targets containing id/provider/to. Windows default to 300 seconds (60–86400); default outcomes are failure/denied, while an empty outcomes list matches all. Rule and target IDs must be unique; providers must exist in Reach. Business roles remain application choices.
|
|
182
|
+
|
|
183
|
+
`audit.checkAlerts({signal?})` checks committed events in the current and previous fixed windows. Rolled-back events do not notify; downtime beyond two windows does not replay historical alerts. This is a fixed-window threshold check. maxNotificationsPerCheck defaults to 100; report.more indicates remaining targets. Shared database claims permit one external attempt per rule/target/window, using the database execution clock in the final atomic claim. Semantic rule or recipient changes create a new policy and may notify again.
|
|
184
|
+
|
|
185
|
+
Messages include only rule/target IDs, level, count and window, without event details or credentials. submitted means provider acceptance; queued means durable Reach enqueue, with queueId for inspection. Neither proves recipient delivery or reading. Failed, interrupted or crashed attempts retain unknown/sending/claimed receipts and are not automatically resent. Inspect audit.alert events and hai_audit_alert_claims, then reconcile with the notification endpoint. Reach-queued messages follow Reach recovery. Claims store no recipient address/body and confirmed submissions/enqueues are cleaned outside the check horizon; unresolved receipts remain for operational review; notification events follow audit retention.
|
|
186
|
+
|
|
187
|
+
alerts.schedule defaults to disabled, once per minute in UTC. audit.schedulerPlan composes enabled backup and alert tasks for explicit host Scheduler initialization; it is null without automatic tasks. Scheduler callbacks fail when checking fails or report.failed>0. Close Scheduler before Audit/Reach. Audit cancels further claims and drains in-flight sends within Reach provider timeouts without closing host Reach. Query/audit failures return a failed HaiResult; notification failures appear in a successful report.failed and must be checked by callers.
|
|
188
|
+
|
|
189
|
+
## Development and testing
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pnpm --filter @h-ai/audit test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Source examples initialize RelDB first, check audit results, and check cleanup in reverse dependency order; see the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证).
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
Apache-2.0
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @h-ai/audit
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md) · [@h-ai/reldb](../reldb/README.ja.md)
|
|
6
|
+
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
9
|
+
統一された監査ログモジュールです。`audit` オブジェクトで記録、検索、削除、集計を行い、`@h-ai/reldb` で永続化します。
|
|
10
|
+
|
|
11
|
+
SQLite、PostgreSQL、MySQL で読み取り契約は共通です。`createdAt` は `Date`、`details` は JSON 文字列または `null`、値のない nullable フィールドは `null` を返します。日時フィルターは書き込みと同じドライバー変換を使用します。件数は安全な整数とし、範囲外では `STATS_FAILED` を返します。
|
|
12
|
+
|
|
13
|
+
## 主な機能
|
|
14
|
+
|
|
15
|
+
- ユーザー操作やセキュリティイベントを記録し、ユーザー・日時・操作で検索。
|
|
16
|
+
- ログのページ取得、統計、保存期間に応じた削除。
|
|
17
|
+
- 関係データベースに保存し、認証情報を記録前にマスク。
|
|
18
|
+
|
|
19
|
+
## 使い始める
|
|
20
|
+
|
|
21
|
+
### 依存関係
|
|
22
|
+
|
|
23
|
+
- `@h-ai/reldb`:監査ログを永続化するデータベース。**audit.init() より先に初期化してください**。
|
|
24
|
+
|
|
25
|
+
### 用途
|
|
26
|
+
|
|
27
|
+
- ユーザー操作の監査(ログイン、ログアウト、登録、パスワードリセットなど)
|
|
28
|
+
- CRUD 操作の監査(リソースの作成、参照、更新、削除)
|
|
29
|
+
- セキュリティイベントの追跡とコンプライアンス監査
|
|
30
|
+
- 運用統計(操作頻度、アクティブユーザーなど)
|
|
31
|
+
|
|
32
|
+
### クイックスタート
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { audit } from '@h-ai/audit'
|
|
36
|
+
import { reldb } from '@h-ai/reldb'
|
|
37
|
+
|
|
38
|
+
// 1. 初始化依赖
|
|
39
|
+
await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
40
|
+
|
|
41
|
+
// 2. 初始化审计模块(自动使用已初始化的 reldb 单例)
|
|
42
|
+
const result = await audit.init()
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
throw new Error(result.error.message)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. 记录审计日志
|
|
48
|
+
await audit.log({
|
|
49
|
+
userId: 'user_1',
|
|
50
|
+
action: 'login',
|
|
51
|
+
resource: 'auth',
|
|
52
|
+
ipAddress: '127.0.0.1',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 4. 使用便捷方法
|
|
56
|
+
await audit.helper.login('user_1', '127.0.0.1')
|
|
57
|
+
await audit.helper.crud({ userId: 'user_1', action: 'create', resource: 'users', resourceId: 'user_2', details: { name: '张三' } })
|
|
58
|
+
|
|
59
|
+
// 5. 查询日志
|
|
60
|
+
const logs = await audit.list({ pageSize: 20, action: 'login' })
|
|
61
|
+
|
|
62
|
+
// 6. 获取统计
|
|
63
|
+
const stats = await audit.getStats(7) // 最近 7 天
|
|
64
|
+
|
|
65
|
+
// 7. 清理旧日志
|
|
66
|
+
await audit.cleanup(90) // 清理 90 天前的日志
|
|
67
|
+
|
|
68
|
+
// 8. 关闭
|
|
69
|
+
const closeResult = await audit.close()
|
|
70
|
+
if (!closeResult.success) {
|
|
71
|
+
throw new Error(closeResult.error.message)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 初期化設定
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// 默认配置(无需传参)
|
|
79
|
+
await audit.init()
|
|
80
|
+
|
|
81
|
+
// 自定义用户表映射
|
|
82
|
+
await audit.init({
|
|
83
|
+
userTable: 'hai_iam_users', // 用户表名(默认 'hai_iam_users')
|
|
84
|
+
userIdColumn: 'id', // 用户表主键列名(默认 'id')
|
|
85
|
+
userNameColumn: 'username', // 用户表用户名列名(默认 'username')
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
監査ログは固定の `hai_audit_logs` テーブルに保存され、初期化済みの `@h-ai/reldb` が必要です。
|
|
90
|
+
|
|
91
|
+
### API 一覧
|
|
92
|
+
|
|
93
|
+
- `audit.init(config)`:監査モジュールを初期化。
|
|
94
|
+
- `audit.log(input)`:監査ログを記録。
|
|
95
|
+
- `audit.list(options)`:`PaginatedResult<AuditLogWithUser>`(items/total/page/pageSize、ユーザー名 JOIN)を返します。core がページ入力を正規化し、既定 pageSize は 20 です。返されたメタデータを直接利用してください。
|
|
96
|
+
- `audit.getUserRecent(userId, limit)`:ユーザーの最近の操作を取得。
|
|
97
|
+
- `audit.cleanup(days)`:古いログを削除。
|
|
98
|
+
- `audit.getStats(days)`:操作統計を取得。
|
|
99
|
+
- `audit.helper.login/logout/register/...`:記録用ヘルパー。
|
|
100
|
+
- `audit.close()`:モジュールを終了。
|
|
101
|
+
|
|
102
|
+
一般的なイベントは `audit.helper` で記録し、対応する `AuditHelper` 型は公開を維持します。内部の組み立て用ファクトリーはパッケージ API に含めません。
|
|
103
|
+
|
|
104
|
+
#### 入力制約
|
|
105
|
+
|
|
106
|
+
- `audit.log(input)`:`action` / `resource` は空でない文字列で、長さは 256 文字以内。
|
|
107
|
+
- `audit.log(input)`:`details` 内の認証情報(`password` / `token` / `secret` など)は永続化前に自動的にマスクされます。メールアドレス・電話番号などの業務個人情報も既定で隠し、privacy で調整できます。
|
|
108
|
+
- `audit.list(options)`:`startDate` と `endDate` を両方指定する場合は `startDate <= endDate` が必要です。
|
|
109
|
+
- `audit.getUserRecent(userId, limit)`:`userId` は空でない文字列。`limit` を指定する場合は正の整数。
|
|
110
|
+
- `audit.cleanup(days)`:`days` を指定する場合は 0 以上の整数。
|
|
111
|
+
- `audit.getStats(days)`:`days` を指定する場合は 0 以上の整数。
|
|
112
|
+
|
|
113
|
+
### エラーコード
|
|
114
|
+
|
|
115
|
+
業務データ操作は `HaiResult<T>` を返します。主なエラーコード:
|
|
116
|
+
|
|
117
|
+
| エラーコード | code | 説明 |
|
|
118
|
+
| -------------------------------- | --------------- | -------------------------- |
|
|
119
|
+
| `HaiAuditError.LOG_FAILED` | `hai:audit:001` | 記録に失敗 |
|
|
120
|
+
| `HaiAuditError.QUERY_FAILED` | `hai:audit:002` | 検索に失敗 |
|
|
121
|
+
| `HaiAuditError.CLEANUP_FAILED` | `hai:audit:003` | 削除に失敗 |
|
|
122
|
+
| `HaiAuditError.STATS_FAILED` | `hai:audit:004` | 集計に失敗 |
|
|
123
|
+
| `HaiAuditError.INIT_IN_PROGRESS` | `hai:audit:005` | 初期化中 |
|
|
124
|
+
| `HaiAuditError.NOT_INITIALIZED` | `hai:audit:010` | モジュールが未初期化 |
|
|
125
|
+
| `HaiAuditError.CONFIG_ERROR` | `hai:audit:012` | 設定エラー |
|
|
126
|
+
| `HaiAuditError.BACKUP_DISABLED` | `hai:audit:020` | バックアップ無効 |
|
|
127
|
+
| `HaiAuditError.BACKUP_FAILED` | `hai:audit:021` | バックアップ/検証失敗 |
|
|
128
|
+
| `HaiAuditError.BACKUP_INVALID` | `hai:audit:022` | バックアップ入力が無効 |
|
|
129
|
+
| `HaiAuditError.BACKUP_CONFLICT` | `hai:audit:023` | IDとエクスポート条件が衝突 |
|
|
130
|
+
| `HaiAuditError.ALERTS_DISABLED` | `hai:audit:024` | アラート無効 |
|
|
131
|
+
| `HaiAuditError.ALERTS_FAILED` | `hai:audit:025` | アラート検査・通知失敗 |
|
|
132
|
+
|
|
133
|
+
> 注:`audit.close()` も `HaiResult<void>` を返します。`audit.helper.passwordResetRequest()` のメール情報は既定の業務個人情報設定でマスクされます。
|
|
134
|
+
|
|
135
|
+
`audit.init()` は自動建表とリポジトリーの準備完了を待って成功します。DDL 失敗時は `isInitialized=false` のままで、DB を修正後に再試行できます。独自 JOIN・統計・削除も同じ準備完了を待ちます。初期化中の close は遅れた公開を無効化します。init の HaiResult を確認後に記録・検索し、共有 DB の接続は所有しないため audit の終了後に DB を終了します。
|
|
136
|
+
|
|
137
|
+
## イベント文脈、個人情報と保持
|
|
138
|
+
|
|
139
|
+
監査 createdAt、アラート窓、アーカイブ終了時刻はノード時刻ではなく DB 時刻を共用します。バックアップ/アラート定時タスクは永続的な重複排除の保証により冪等を宣言し、失敗を非冪等の不明タスクとして後続スケジュールを阻止しません。
|
|
140
|
+
|
|
141
|
+
`capture: { enabled: true, includeReads: false }` でフレームワークの自動記録を制御します。IAM はアカウント、認証、パスワード、権限、API Key、セッションの重要変更を記録します。Serv/Kit は検証済み身元と信頼できる接続元を関連付け、書込と失敗・拒否を記録し、成功した読取は既定で省略します。身元確定前のプロトコル・入力検証失敗は匿名です。HTTP イベントは操作・メソッド・状態・理由コードのみを記録し、要求本文・トークン・内部エラーは複製しません。業務の完了・失敗境界ではアプリが `audit.log()` を使います。自動記録の無効化は明示的 log に影響しません。audit は IAM より先に初期化し、IAM の後に閉じます。信頼できるジョブ/CLI は実行主体と関連文脈を設定してください。
|
|
142
|
+
|
|
143
|
+
`audit.log(input, tx)` と `audit.capture(input, { tx })` は既存の RelDB トランザクションを使い、変更と一緒にコミット・ロールバックします。独立コミットやロールバック後の成功記録は残しません。失敗・拒否はロールバック完了後に呼出境界で記録してください。自動記録失敗は関連診断を出し、既に反映された業務結果は維持して危険な再試行を防ぎます。記録を必須とする業務は、同じトランザクション内で明示的 log の HaiResult を確認してからコミットします。
|
|
144
|
+
|
|
145
|
+
新規記録は category(system/business)、outcome(success/failure/denied)、severity(info/warning/error/critical)、要求・追跡・範囲 ID と actorSnapshot を持ちます。イベント時点の身元を複製し、名前・役割の変更や削除後も保持します。userId のみなら設定された名前を取得し、取得失敗時は名前不明を明示して診断を出します。HTTP ホストは検証済み身元で `audit.withContext({ actor, requestId, ipAddress, userAgent }, operation)` を使います。この文脈ツールは操作の戻り値・例外を維持し、業務書込は HaiResult を返します。
|
|
146
|
+
|
|
147
|
+
`audit.list({ category, outcome, severity, scopeId, requestId, traceId, sortBy, sortDirection })` は分類検索と制御された並べ替え、`getStats(7, { category: 'system' })` は分類内の操作集計です。業務個人情報は既定で隠し、`privacy.fields` で項目名を追加できます。無効にしても password/token は常に隠します。自由文に個人情報がある場合は対応項目全体を隠してください。
|
|
148
|
+
|
|
149
|
+
`retention: { enabled: true, minDays: 90 }` は共有DBの最低保持を有効にします。cleanup(0) も最低期間を守り、トリガーは保護対象の UPDATE・DELETE・衝突置換を拒否します。未設定の新規フレームワークは保持保護を強制せず、削除引数の既定90日だけでは不変保証になりません。retention を省略した再初期化はDB設定を継続し、信頼できる起動設定で明示的に無効化できます。通常の行書込を対象とし、DB所有者のDDL・権限や独立アーカイブなどは配備側で管理します。
|
|
150
|
+
|
|
151
|
+
更新前に旧書込を停止してバックアップし、`migrateAudit({ dryRun: true })` で読み取り専用の事前確認ができます。audit.init は再実行可能な移行を行います。歴史上不明な分類・結果・身元は null のままです。旧記録のユーザー表JOINは表示互換であり、当時の身元の証拠にはしません。書込失敗は失敗結果と関連診断を返し、close は開始済み書込を待つため、その後に RelDB を閉じてください。
|
|
152
|
+
|
|
153
|
+
マスキングは項目全体に適用し、オブジェクト、配列、独自 JSON 変換の機密値も隠します。未初期化または close 後の capture は未初期化エラーを返し、初期化済みモジュールで capture を無効にした場合だけ自動イベントを省略します。
|
|
154
|
+
|
|
155
|
+
## 非公開バックアップと定時アーカイブ
|
|
156
|
+
|
|
157
|
+
`backup.enabled` は既定で無効です。RelDB を先に初期化し、初期化済み StorageFunctions を `audit.init({ storage, backup: { enabled: true } })` に渡します。batchSize は走査件数(既定200)、maxRecordBytes は UTF-8 一行の上限(既定1 MiB)です。上限超過は失敗し、元の監査証拠は切り詰めません。
|
|
158
|
+
|
|
159
|
+
`audit.backup.run({ id?, from?, to?, signal? })` は非公開 JSONL と ID/key、件数、SHA-256、開始/対象終了日時を返します。名前空間は AUDIT_BACKUP_PREFIX(hai-private/audit-backups/)に固定します。同じ実行は安定 ID を使い、既存の完全なアーカイブを検証して再利用します。条件の衝突・不完全・破損は失敗し、上書きしません。条件書込の競合や結果不明は実ファイルで確認します。`audit.backup.verify(key, { signal? })` は有界ストリームで形式・件数・チェックサムを検証します。チェックサムは完全性を示し、真正性を保証しません。
|
|
160
|
+
|
|
161
|
+
v1 は header、`{type:'entry',record}`、footer の行形式です。元の ID、ISO 日付、保存済み詳細、歴史上不明の身元を維持し、現在のユーザー名とは結合しません。footer の SHA-256 はそれ以前の UTF-8 行と改行を含み、recordCount は entry 件数です。検証後にオフライン参照や独立した検索ストアへ復元でき、保護されたオンライン記録は置換しません。オンライン出力は固定終了時刻と主キーカーソルによる実走査範囲です。同一時点への完全復旧には書込/削除を停止するか DB ネイティブの一貫したスナップショットを使います。
|
|
162
|
+
|
|
163
|
+
backup.schedule は enabled、5項目 cron(既定 `0 2 * * *`)、IANA timezone(既定 UTC)です。audit.schedulerPlan は有効なバックアップとアラートをまとめた tasks/hooks 計画を返し、自動タスクがなければ null です。コールバックは共有 occurrence ID と取消信号を使い、失敗時に例外を投げてタスクを失敗にします。結果不明は Scheduler の手順で確認します。example/fullstack は組み込み済みで、他のホストは既存のタスク/コールバックと合成します。Scheduler、IAM、Audit、Storage、RelDB の順に閉じます。Audit は自分のバックアップを取消・排出し、ホスト Storage は閉じません。
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const plan = audit.schedulerPlan
|
|
167
|
+
if (plan) {
|
|
168
|
+
const initialized = await scheduler.init({ ...schedulerSettings, ...plan })
|
|
169
|
+
if (!initialized.success)
|
|
170
|
+
throw new Error(initialized.error.message)
|
|
171
|
+
const started = scheduler.start()
|
|
172
|
+
if (!started.success)
|
|
173
|
+
throw new Error(started.error.message)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
ホストが audit/scheduler と検証済み schedulerSettings を提供し、Audit/Storage/RelDB を初期化済みとします。非公開ストレージを使い、バックアップ有効時は S3 publicUrl 設定を拒否します。バケット/CDN のアクセス制限も必要です。公開ファイル入口は指定資源のみを許可し、example/fullstack は正規のアバターパスだけを公開します。アーカイブの取得には監査の認可入口を使い、無効化後も既存ファイルは非公開のままです。
|
|
178
|
+
|
|
179
|
+
## 等級別の異常アラート
|
|
180
|
+
|
|
181
|
+
`alerts.enabled` は既定で false。有効化時は初期化済み Reach を `audit.init({ reach, alerts: { enabled: true, rules: [...] } })` に渡します。ルールは一意な id、完全一致の actions、任意の resource、outcomes、minimumSeverity、threshold/windowSeconds、通知 severity、id/provider/to を持つ targets で指定します。既定の窓は300秒(60–86400秒)、結果は failure/denied、空の outcomes は全結果です。通知 Provider は Reach に登録済みである必要があります。業務ロールはアプリ側で決定します。
|
|
182
|
+
|
|
183
|
+
`audit.checkAlerts({signal?})` はコミット済みイベントの現在と直前の固定窓を確認します。ロールバックは通知せず、二窓を超えた停止の履歴は再送しません。maxNotificationsPerCheck は既定100、report.more は次回の未処理対象を示します。共有 DB でルール・対象・窓ごとに外部試行を一度だけ永続取得し、最終 SQL で DB 実行時刻を検証します。ルールの意味や送信先を変更すると別方針となり、新規通知する場合があります。
|
|
184
|
+
|
|
185
|
+
通知はルール/対象 ID、等級、件数、期間のみで、イベント詳細や資格情報を複製しません。submitted は Provider の受付、queued は Reach の永続キュー登録であり、到達や既読を保証しません。queueId で照会できます。失敗・中断・クラッシュは unknown/sending/claimed 結果記録を残し、不確定な外部効果を自動再送しません。audit.alert と hai_audit_alert_claims を確認し、通知先と照合して対処します。Reach 登録済みキューは Reach の復旧規則に従います。取得表は宛先/本文を保存せず、確認範囲外の受付/キュー登録済み取得のみ清掃し、未決は運用確認まで保持し、通知イベントは監査保留に従います。
|
|
186
|
+
|
|
187
|
+
alerts.schedule は独立して有効化でき、既定は毎分・UTC。audit.schedulerPlan は有効なバックアップ/アラートをまとめ、ホストが Scheduler に明示的に渡します。自動タスクがなければ null。検査失敗または report.failed>0 はタスク失敗です。Scheduler を先に停止し、次に Audit/Reach を閉じます。Audit は新規取得を取消し、Reach の Provider タイムアウト内で実行中通知を待機し、ホスト Reach は閉じません。DB/監査失敗は HaiResult の失敗、通知失敗は成功レポートの failed に現れるため、呼出側で確認します。
|
|
188
|
+
|
|
189
|
+
## 開発とテスト
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pnpm --filter @h-ai/audit test
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
ソース例は RelDB を先に初期化し、監査結果と依存関係の逆順による終了結果を確認します。[JSDoc 規約(中国語)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) を参照してください。
|
|
196
|
+
|
|
197
|
+
## ライセンス
|
|
198
|
+
|
|
199
|
+
Apache-2.0
|
package/README.md
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
# @h-ai/audit
|
|
2
2
|
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md) · [@h-ai/reldb](../reldb/README.md)
|
|
6
|
+
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
3
9
|
统一审计日志模块,通过 `audit` 对象提供审计日志的记录、查询、清理与统计功能,基于 `@h-ai/reldb` 实现持久化。
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
SQLite、PostgreSQL、MySQL 的读取结果保持同一契约:`createdAt` 为 `Date`,`details` 为 JSON 字符串或 `null`,缺失的可空字段返回 `null`。日期筛选与写入使用相同的驱动日期转换;统计数量为安全整数,超出范围返回 `STATS_FAILED`。
|
|
12
|
+
|
|
13
|
+
## 核心功能清单
|
|
14
|
+
|
|
15
|
+
- 记录用户操作与安全事件,支持按用户、时间和操作筛选。
|
|
16
|
+
- 查询分页日志与操作统计,并按保留天数清理。
|
|
17
|
+
- 使用关系数据库持久化,写入前隐藏凭证类字段。
|
|
18
|
+
|
|
19
|
+
## 使用入门
|
|
20
|
+
|
|
21
|
+
### 依赖
|
|
6
22
|
|
|
7
23
|
- `@h-ai/reldb` — 数据库(审计日志持久化),**需在 audit.init() 前初始化**
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
### 适用场景
|
|
10
26
|
|
|
11
27
|
- 用户操作审计(登录、登出、注册、密码重置等)
|
|
12
28
|
- CRUD 操作审计(创建、读取、更新、删除资源)
|
|
13
29
|
- 安全事件追踪与合规审计
|
|
14
30
|
- 运营数据统计(操作频次、活跃用户等)
|
|
15
31
|
|
|
16
|
-
|
|
32
|
+
### 快速开始
|
|
17
33
|
|
|
18
34
|
```ts
|
|
19
35
|
import { audit } from '@h-ai/audit'
|
|
@@ -50,10 +66,13 @@ const stats = await audit.getStats(7) // 最近 7 天
|
|
|
50
66
|
await audit.cleanup(90) // 清理 90 天前的日志
|
|
51
67
|
|
|
52
68
|
// 8. 关闭
|
|
53
|
-
await audit.close()
|
|
69
|
+
const closeResult = await audit.close()
|
|
70
|
+
if (!closeResult.success) {
|
|
71
|
+
throw new Error(closeResult.error.message)
|
|
72
|
+
}
|
|
54
73
|
```
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
### 初始化配置
|
|
57
76
|
|
|
58
77
|
```ts
|
|
59
78
|
// 默认配置(无需传参)
|
|
@@ -69,45 +88,112 @@ await audit.init({
|
|
|
69
88
|
|
|
70
89
|
审计日志固定存储在 `hai_audit_logs` 表中,依赖 `@h-ai/reldb` 已初始化。
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
### API 概览
|
|
73
92
|
|
|
74
93
|
- `audit.init(config)` - 初始化审计模块
|
|
75
94
|
- `audit.log(input)` - 记录审计日志
|
|
76
|
-
- `audit.list(options)` -
|
|
95
|
+
- `audit.list(options)` - 返回统一 `PaginatedResult<AuditLogWithUser>`(items/total/page/pageSize,含用户名 JOIN);页码/条数复用 core 分页规范化,默认 pageSize 为 20,调用方直接使用返回元数据。
|
|
77
96
|
- `audit.getUserRecent(userId, limit)` - 获取用户最近活动
|
|
78
97
|
- `audit.cleanup(days)` - 清理旧日志
|
|
79
|
-
- `audit.getStats(days)` -
|
|
98
|
+
- `audit.getStats(days, filters)` - 获取按分类等条件筛选的操作统计
|
|
80
99
|
- `audit.helper.login/logout/register/...` - 便捷记录器
|
|
81
100
|
- `audit.close()` - 关闭模块
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
常见事件通过 `audit.helper` 记录,对应的 `AuditHelper` 类型保持公开;便捷记录器的内部装配工厂不属于包 API。
|
|
103
|
+
|
|
104
|
+
#### 输入约束
|
|
84
105
|
|
|
85
106
|
- `audit.log(input)`:`action` / `resource` 必须为非空字符串,且长度不超过 256。
|
|
107
|
+
- `audit.log(input)`:`details` 中的凭证类敏感字段(如 `password` / `token` / `secret`)会在持久化前自动脱敏;业务隐私字段(如邮箱、手机号)默认同时隐藏,可通过 privacy 配置调整。
|
|
86
108
|
- `audit.list(options)`:若同时传 `startDate` 与 `endDate`,必须满足 `startDate <= endDate`。
|
|
87
109
|
- `audit.getUserRecent(userId, limit)`:`userId` 必须为非空字符串;`limit`(如传入)必须为正整数。
|
|
88
110
|
- `audit.cleanup(days)`:`days`(如传入)必须为非负整数。
|
|
89
111
|
- `audit.getStats(days)`:`days`(如传入)必须为非负整数。
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
### 错误码
|
|
114
|
+
|
|
115
|
+
业务数据操作返回 `HaiResult<T>`,常用错误码如下:
|
|
116
|
+
|
|
117
|
+
| 错误码 | code | 说明 |
|
|
118
|
+
| -------------------------------- | --------------- | ---------------------- |
|
|
119
|
+
| `HaiAuditError.LOG_FAILED` | `hai:audit:001` | 记录失败 |
|
|
120
|
+
| `HaiAuditError.QUERY_FAILED` | `hai:audit:002` | 查询失败 |
|
|
121
|
+
| `HaiAuditError.CLEANUP_FAILED` | `hai:audit:003` | 清理失败 |
|
|
122
|
+
| `HaiAuditError.STATS_FAILED` | `hai:audit:004` | 统计失败 |
|
|
123
|
+
| `HaiAuditError.INIT_IN_PROGRESS` | `hai:audit:005` | 初始化进行中 |
|
|
124
|
+
| `HaiAuditError.NOT_INITIALIZED` | `hai:audit:010` | 模块未初始化 |
|
|
125
|
+
| `HaiAuditError.CONFIG_ERROR` | `hai:audit:012` | 配置错误 |
|
|
126
|
+
| `HaiAuditError.BACKUP_DISABLED` | `hai:audit:020` | 备份未启用 |
|
|
127
|
+
| `HaiAuditError.BACKUP_FAILED` | `hai:audit:021` | 备份或校验失败 |
|
|
128
|
+
| `HaiAuditError.BACKUP_INVALID` | `hai:audit:022` | 备份输入无效 |
|
|
129
|
+
| `HaiAuditError.BACKUP_CONFLICT` | `hai:audit:023` | 备份编号与导出条件冲突 |
|
|
130
|
+
| `HaiAuditError.ALERTS_DISABLED` | `hai:audit:024` | 告警未启用 |
|
|
131
|
+
| `HaiAuditError.ALERTS_FAILED` | `hai:audit:025` | 告警检查或通知失败 |
|
|
132
|
+
|
|
133
|
+
> 说明:`audit.close()` 也返回 `HaiResult<void>`;`audit.helper.passwordResetRequest()` 的邮箱详情默认经过业务隐私脱敏。
|
|
134
|
+
|
|
135
|
+
`audit.init()` 会等待自动建表和仓库就绪才返回成功;DDL 失败保持 `isInitialized=false`,修正数据库后可重试。自定义 JOIN、统计和清理也使用同一就绪屏障;初始化期间关闭会使迟到结果失效。先检查 init 的 HaiResult,再立即记录或查询;audit 不拥有共享 RelDB 的连接,关闭 audit 后再关闭数据库。
|
|
136
|
+
|
|
137
|
+
## 事件上下文、隐私与保留
|
|
138
|
+
|
|
139
|
+
审计 createdAt、告警窗口与归档截止共用数据库时钟,不以各节点系统钟作为共享真值。备份与告警调度任务使用已验证的持久去重保障声明幂等;失败不会被错误地当作非幂等未知任务而阻塞后续调度。
|
|
140
|
+
|
|
141
|
+
`capture: { enabled: true, includeReads: false }` 控制框架自动采集。IAM 记录关键账号、鉴别、密码、授权、API Key 和会话事件;Serv/Kit 绑定已验证身份与可信地址,记录写请求及失败/拒绝,成功读取默认跳过。协议/输入校验前尚未确认身份时保留匿名事件。HTTP 入口只记录操作、方法、状态或原因码,不复制请求体、令牌和错误详情。业务含义仍由应用在完成/失败边界调用 `audit.log()` 记录;关闭自动采集不影响显式 log。审计应先于 IAM 初始化,并在 IAM 关闭后关闭。可信任务/CLI 应显式提供操作身份和关联上下文。
|
|
142
|
+
|
|
143
|
+
`audit.log(input, tx)` 与 `audit.capture(input, { tx })` 可使用调用者现有 RelDB 事务;事件与变更一并提交或回滚,不独立提交,也不留下回滚成功的假记录。事务回滚会移除其中的事件,调用入口应在回滚完成后记录失败/拒绝。自动采集写入失败会输出关联诊断,已生效的业务不会被改写成可盲目重试的失败;需要强制审计的业务应在同一事务检查显式 log 的 HaiResult 后再提交。
|
|
144
|
+
|
|
145
|
+
新记录包含 `category`(system/business)、`outcome`(success/failure/denied)、`severity`(info/warning/error/critical)、请求/链路/范围标识和 `actorSnapshot`。身份快照在事件发生时复制,改名、改角色或删除账号不会改写它。只传 userId 时按配置映射尝试捕获用户名;映射不可读时记录明确缺少名称的身份并输出诊断。可信 HTTP 宿主应通过 `audit.withContext({ actor, requestId, ipAddress, userAgent }, operation)` 提供完整上下文,不能用未验证的请求体充当操作者。该工具保留 operation 的同步/异步结果及异常,业务写入仍返回 HaiResult。
|
|
92
146
|
|
|
93
|
-
|
|
147
|
+
`audit.list({ category, outcome, severity, scopeId, requestId, traceId, sortBy, sortDirection })` 支持分类筛选和受控排序,`getStats(7, { category: 'system' })` 统计指定类别的操作。`privacy.enabled` 默认开启,隐藏邮箱、电话等业务字段;`privacy.fields` 可补充项目字段名。凭据始终脱敏,关闭业务隐私不会泄漏 password/token。自由文本包含隐私时应配置整个对应字段隐藏。
|
|
94
148
|
|
|
95
|
-
|
|
96
|
-
| -------------------------------- | --------------- | ------------ |
|
|
97
|
-
| `HaiAuditError.LOG_FAILED` | `hai:audit:001` | 记录失败 |
|
|
98
|
-
| `HaiAuditError.QUERY_FAILED` | `hai:audit:002` | 查询失败 |
|
|
99
|
-
| `HaiAuditError.CLEANUP_FAILED` | `hai:audit:003` | 清理失败 |
|
|
100
|
-
| `HaiAuditError.STATS_FAILED` | `hai:audit:004` | 统计失败 |
|
|
101
|
-
| `HaiAuditError.INIT_IN_PROGRESS` | `hai:audit:005` | 初始化进行中 |
|
|
102
|
-
| `HaiAuditError.NOT_INITIALIZED` | `hai:audit:010` | 模块未初始化 |
|
|
103
|
-
| `HaiAuditError.CONFIG_ERROR` | `hai:audit:012` | 配置错误 |
|
|
149
|
+
`retention: { enabled: true, minDays: 90 }` 开启最低保留。共享数据库保存策略,清理使用实际最低期限,`cleanup(0)` 不能绕过;数据库触发器拒绝保护期内的 UPDATE、DELETE 和冲突覆盖写。框架首次未配置时不强制保护,清理参数默认90天本身不构成不可变保证。未传 retention 的重初始化沿用既有策略,只有可信启动配置显式关闭才解除。该保护针对正常行级写入;数据库所有者的 DDL/权限、独立归档和更强存储保护需由部署管理。
|
|
104
150
|
|
|
105
|
-
|
|
151
|
+
升级前先停旧写入并备份,`migrateAudit({ dryRun: true })` 只读预检;audit.init 自动执行可重入升级。历史未知分类、结果和身份快照保持 null,不补造历史身份。列表对无快照的历史记录保留旧的用户表关联展示,不能当作事件时身份。写入失败返回失败并输出关联诊断;close 等待已开始写入后再释放模块,随后才能关闭 RelDB。
|
|
152
|
+
|
|
153
|
+
脱敏匹配整个字段:对象、数组以及自定义 JSON 投影内的敏感值同样隐藏。关闭或尚未初始化 Audit 时,capture 返回未初始化失败;只有已初始化模块的关闭采集配置才跳过自动事件。
|
|
154
|
+
|
|
155
|
+
## 私有备份与定时归档
|
|
156
|
+
|
|
157
|
+
`backup.enabled` 默认关闭。开启时向 `audit.init({ storage, backup: { enabled: true } })` 注入已经初始化的 `StorageFunctions`;数据库仍先初始化。`backup.batchSize` 控制扫描批量(默认200),`maxRecordBytes` 限制单条 UTF-8 记录(默认1 MiB),超限返回失败并保留原始审计,不截断证据。
|
|
158
|
+
|
|
159
|
+
`audit.backup.run({ id?, from?, to?, signal? })` 输出私有 JSONL 文件并返回 ID、key、记录数、SHA-256、开始和截止时间。固定命名空间为 `AUDIT_BACKUP_PREFIX`(hai-private/audit-backups/)。同一执行使用稳定 ID;已有完整归档校验后复用,导出条件冲突、文件不完整或损坏均返回失败,不能覆盖。条件写竞争与写结果未知时核对实际文件。`audit.backup.verify(key, { signal? })` 有界流式核对格式、记录数和校验和;校验和用于完整性,不代表来源真实性。
|
|
160
|
+
|
|
161
|
+
归档 v1 按行保存 header、`{type:'entry',record}` 和 footer。记录保留原始 ID、ISO 日期、已存详情及可空历史身份;不关联当前用户名。footer 的 SHA-256 覆盖之前所有 UTF-8 行及换行,且 recordCount 等于 entry 数量。可核对后离线读取或恢复至独立查询存储;不覆盖在线受保护记录。在线导出按固定截止时间与主键游标扫描,包含范围以实际扫描为准;需要同一时点的全量恢复时先暂停写入和清理,或采用数据库原生一致性快照。
|
|
162
|
+
|
|
163
|
+
定时配置位于 `backup.schedule`:`enabled`、五字段 cron(默认 `0 2 * * *`)、IANA timezone(默认 UTC)。开启后 `audit.schedulerPlan` 返回可显式合并进 `scheduler.init()` 的 tasks/hooks 计划;没有自动任务时为 null。回调复用 Scheduler 的共享执行编号和取消信号,并在失败时抛错使任务准确失败;未知执行按 Scheduler 规则核对,不能盲目重跑。示例/fullstack 已装配此计划,其它宿主显式合并已有任务与回调。先关闭 Scheduler,再关闭 IAM、Audit、Storage 和 RelDB;Audit 取消并等待自己的备份,不关闭宿主 Storage。
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const plan = audit.schedulerPlan
|
|
167
|
+
if (plan) {
|
|
168
|
+
const initialized = await scheduler.init({ ...schedulerSettings, ...plan })
|
|
169
|
+
if (!initialized.success)
|
|
170
|
+
throw new Error(initialized.error.message)
|
|
171
|
+
const started = scheduler.start()
|
|
172
|
+
if (!started.success)
|
|
173
|
+
throw new Error(started.error.message)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
上例由宿主导入 `audit`、`scheduler` 并提供已校验的 schedulerSettings,Audit/Storage/RelDB 已初始化。归档使用私有存储,开启备份时拒绝配置了公开 publicUrl 的 S3;部署仍须限制桶/CDN访问。公开静态文件入口应仅开放指定资源;管理示例与 fullstack 仅公开规范头像路径,如提供归档下载,须通过审计授权接口处理,关闭备份也不改变已有文件的私有边界。
|
|
178
|
+
|
|
179
|
+
## 分级异常告警
|
|
180
|
+
|
|
181
|
+
`alerts.enabled` 默认关闭。开启时注入已初始化的 Reach:`audit.init({ reach, alerts: { enabled: true, rules: [...] } })`。规则通过稳定 id、actions(精确事件名)、可选 resource、outcomes、minimumSeverity 筛选事件,threshold/windowSeconds 指定固定窗口阈值,severity 指定通知等级,targets 中的 id/provider/to 指定人员或目标。默认窗口300秒,范围60–86400秒;默认仅失败/拒绝,空 outcomes 表示所有结果。规则和目标 id 必须各自唯一;provider 须已在 Reach 注册。不将具体业务角色写入框架。
|
|
182
|
+
|
|
183
|
+
`audit.checkAlerts({signal?})` 检查已提交事件的当前及前一个固定窗口,回滚事件不触发;停机超过两窗不补发历史通知。它不是滑动窗口或完整监控系统。默认单轮最多尝试100次,maxNotificationsPerCheck 可配置,report.more 表示仍有目标待后续检查。已达阈值的规则/目标/窗口在共享数据库原子领取,一次外部尝试前持久化;领取同时核验数据库执行时钟,避免节点偏差或挂起恢复重发。规则语义或通知目标变更形成新策略,可能产生新的通知。
|
|
184
|
+
|
|
185
|
+
通知只包含规则/目标编号、等级、计数与窗口,不复制审计详情或凭据。submitted 表示 Provider 接受,queued 仅表示 Reach 持久入队,可通过 queueId 查询;都不等于人员已收到或阅读。失败/中断/崩溃留下 unknown、sending 或 claimed 回执,不自动重发不确定外部效果。运维先检查 `audit.alert` 事件与 `hai_audit_alert_claims`,核验通知端后处理;Reach 已入队的消息遵循其自身恢复规则。领取表不保存地址/正文,查询范围外已确认提交/入队的领取定期清理;未决回执保留供运维核验,通知审计遵守审计保留策略。
|
|
186
|
+
|
|
187
|
+
`alerts.schedule` 可独立启用,默认每分钟、UTC。`audit.schedulerPlan` 合并已启用的备份和告警任务,由宿主显式交给 Scheduler;计划没有自动任务时为 null。调度回调在检查失败或 failed>0 时准确失败。先关闭 Scheduler,再关闭 Audit 和 Reach;关闭 Audit 取消后续领取并等待在途通知完成(受 Reach Provider 超时约束),不关闭宿主 Reach。数据库查询/审计失败返回失败;通知业务失败在成功检查报告的 failed 字段体现,调用方必须检查。
|
|
188
|
+
|
|
189
|
+
## 开发测试
|
|
106
190
|
|
|
107
191
|
```bash
|
|
108
192
|
pnpm --filter @h-ai/audit test
|
|
109
193
|
```
|
|
110
194
|
|
|
111
|
-
|
|
195
|
+
审计源码示例明确先初始化 RelDB、检查审计写入结果,再按依赖反序检查关闭结果;参见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。
|
|
196
|
+
|
|
197
|
+
## 许可证
|
|
112
198
|
|
|
113
199
|
Apache-2.0
|