Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# @suaveplan/audit-logging - Tamper-proof, GDPR-compliant audit logging: cryptographic chain-integrity, pluggable storage, retention policy, GDPR export, and a rich event taxonomy
|
||||
|
||||
## Summary
|
||||
|
||||
Tamper-proof, GDPR-compliant audit logging for Suaveplan. Provides a cryptographic chain-integrity engine, pluggable storage adapters, GDPR data export, configurable retention policies, and a rich event taxonomy spanning auth, authorization, data access, system, and GDPR events.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [errors](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [audit-error](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [audit-logging.schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-error](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logging.schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-error](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logging.schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-error](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logging.schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [memory](./src/adapters/memory/memory.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_ - [storage](./src/adapters/storage/storage.md) — <!-- begin:auto-docs --> _(no submodules)_- [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_ - [audit-logger](./src/core/audit-logger/audit-logger.md) — <!-- begin:auto-docs --> _(no submodules)_- [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_ - [chain-integrity](./src/core/chain-integrity/chain-integrity.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [audit-error](./src/errors/audit-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ - [audit-logging.schemas](./src/schemas/audit-logging.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Features
|
||||
|
||||
- **AuditLogger** — orchestrates log, query, verify, export, and retention with injectable dependencies
|
||||
- **Chain integrity** — HMAC-SHA256 signed events linked in a tamper-evident chain via `previousHash`
|
||||
- **MemoryAuditAdapter** — full in-memory storage with rich query filtering for tests and development
|
||||
- **GDPR export** — Article 15/20 compliant `exportGDPRData()` with event summary statistics
|
||||
- **Retention policy** — archive-before-delete with configurable TTL and location
|
||||
- **Zod schemas** — runtime validation for all public types
|
||||
- **OTel instrumentation** — counter `audit.events_logged_total` with `event_type` and `severity` labels
|
||||
- **Event bus integration** — emits `audit:event:logged` after each successful log
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/audit-logging
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import { AuditLogger, MemoryAuditAdapter, AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
|
||||
const logger = new AuditLogger({
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: process.env.AUDIT_SECRET_KEY!,
|
||||
enableChainIntegrity: true,
|
||||
});
|
||||
|
||||
const event = await logger.log({
|
||||
type: AuditEventType.AUTH_LOGIN,
|
||||
severity: AuditSeverity.INFO,
|
||||
actor: { userId: "user-123", ipAddress: "203.0.113.1" },
|
||||
target: { type: "session", id: "sess-456" },
|
||||
});
|
||||
|
||||
console.log(event.hash); // hex HMAC-SHA256
|
||||
|
||||
// Verify chain integrity
|
||||
const events = await logger.query({ order: "asc" });
|
||||
const result = await logger.verifyChainIntegrity(events);
|
||||
console.log(result.valid); // true
|
||||
|
||||
// GDPR export
|
||||
const gdprExport = await logger.exportGDPRData("user-123");
|
||||
console.log(gdprExport.summary.totalEvents);
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| [`src/core/audit-logger/audit-logger.md`](src/core/audit-logger/audit-logger.md) | Main logger class |
|
||||
| [`src/core/chain-integrity/chain-integrity.md`](src/core/chain-integrity/chain-integrity.md) | HMAC chain signing and verification |
|
||||
| [`src/adapters/memory-adapter/memory-adapter.md`](src/adapters/memory-adapter/memory-adapter.md) | In-memory storage adapter |
|
||||
| [`src/adapters/storage-adapter/storage-adapter.md`](src/adapters/storage-adapter/storage-adapter.md) | Storage adapter interface |
|
||||
| [`src/schemas/audit-logging.schemas.md`](src/schemas/audit-logging.schemas.md) | Zod validation schemas |
|
||||
| [`src/errors/audit-error.md`](src/errors/audit-error.md) | Error codes and AuditError class |
|
||||
| [`src/types/audit-logging.types.md`](src/types/audit-logging.types.md) | TypeScript type definitions |
|
||||
|
||||
## Event Taxonomy
|
||||
|
||||
| Category | Events |
|
||||
|----------|--------|
|
||||
| Authentication | `auth_login`, `auth_logout`, `auth_register`, `auth_password_change`, `auth_password_reset`, `auth_mfa_enable`, `auth_mfa_disable`, `auth_session_create`, `auth_session_revoke`, `auth_api_key_create`, `auth_api_key_revoke` |
|
||||
| Authorization | `authz_permission_check`, `authz_permission_grant`, `authz_permission_revoke`, `authz_role_assign`, `authz_role_unassign` |
|
||||
| Data Access | `data_create`, `data_read`, `data_update`, `data_delete`, `data_export`, `data_import` |
|
||||
| System | `system_config_change`, `system_security_alert`, `system_rate_limit`, `system_error` |
|
||||
| GDPR | `gdpr_data_export`, `gdpr_data_delete`, `gdpr_consent_change` |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,116 @@
|
||||
# memory-adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides an in-memory implementation of `AuditStorageAdapter` backed by a `Map<string, AuditEvent>`. Designed for unit tests, integration tests, local development, and single-node scenarios where persistence across process restarts is not required. All filtering, sorting, and pagination are performed in-process without any external dependencies.
|
||||
|
||||
## Features
|
||||
|
||||
- Full `AuditStorageAdapter` implementation — save, query, getLatestEvent, deleteExpired, archive, count
|
||||
- Rich query filtering: event types, userId, sessionId, severity (range-based), targetType, targetId, time range, tenantId
|
||||
- Stable ASC/DESC ordering by timestamp with offset/limit pagination
|
||||
- Separate archive store for events moved by `archive()`
|
||||
- `snapshot()` and `archivedSnapshot()` helpers for test assertions
|
||||
- `clear()` for test teardown
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
import { AuditLogger } from "@suaveplan/audit-logging/core/audit-logger/audit-logger";
|
||||
|
||||
const adapter = new MemoryAuditAdapter();
|
||||
const logger = new AuditLogger({
|
||||
storage: adapter,
|
||||
secretKey: "test-secret",
|
||||
});
|
||||
|
||||
await logger.log({
|
||||
type: "auth_login",
|
||||
severity: "info",
|
||||
actor: { userId: "user-1" },
|
||||
hash: "",
|
||||
});
|
||||
|
||||
const events = adapter.snapshot();
|
||||
console.log(events.length); // 1
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
import { AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
|
||||
const adapter = new MemoryAuditAdapter();
|
||||
|
||||
// Seed events
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await adapter.save({
|
||||
id: `evt-${i}`,
|
||||
type: i % 2 === 0 ? AuditEventType.AUTH_LOGIN : AuditEventType.DATA_READ,
|
||||
severity: AuditSeverity.INFO,
|
||||
actor: { userId: `user-${i % 3}` },
|
||||
timestamp: new Date(Date.now() - i * 60_000),
|
||||
hash: "placeholder",
|
||||
});
|
||||
}
|
||||
|
||||
// Paginated query
|
||||
const page1 = await adapter.query({ limit: 5, offset: 0, order: "desc" });
|
||||
const page2 = await adapter.query({ limit: 5, offset: 5, order: "desc" });
|
||||
|
||||
// Count by user
|
||||
const userCount = await adapter.count({ userId: "user-0" });
|
||||
|
||||
// Archive old events
|
||||
const cutoff = new Date(Date.now() - 10 * 60_000);
|
||||
const archived = await adapter.archive(cutoff, "s3://audit-archive/2024");
|
||||
console.log(`Archived ${archived} events`);
|
||||
|
||||
// Delete remaining old events
|
||||
const deleted = await adapter.deleteExpired(cutoff);
|
||||
console.log(`Deleted ${deleted} expired events`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `MemoryAuditAdapter`
|
||||
|
||||
**`save(event: AuditEvent): Promise<void>`**
|
||||
Stores the event by its `id`. Overwrites if `id` already exists.
|
||||
|
||||
**`query(options: AuditQueryOptions): Promise<AuditEvent[]>`**
|
||||
Filters, sorts, and paginates the in-memory event set. All filter fields are ANDed together.
|
||||
|
||||
- `severity` accepts a single value or array; filters to events matching any of the provided severities
|
||||
- `order` defaults to `"asc"`
|
||||
- `offset` defaults to 0; `limit` defaults to unlimited
|
||||
|
||||
**`getLatestEvent(tenantId?: string): Promise<AuditEvent | null>`**
|
||||
Returns the event with the most recent timestamp, optionally scoped to a tenant. Returns `null` if no events exist.
|
||||
|
||||
**`deleteExpired(before: Date): Promise<number>`**
|
||||
Removes all events where `timestamp < before`. Returns the count deleted.
|
||||
|
||||
**`archive(before: Date, location: string): Promise<number>`**
|
||||
Moves events where `timestamp < before` into the internal archive map (keyed by `location:id`). Returns the count archived.
|
||||
|
||||
**`count(options?: Partial<AuditQueryOptions>): Promise<number>`**
|
||||
Returns the total count of events matching the filter, or total size if no options provided.
|
||||
|
||||
**`snapshot(): AuditEvent[]`**
|
||||
Returns a copy of all currently stored (non-archived) events. For test assertions.
|
||||
|
||||
**`archivedSnapshot(): AuditEvent[]`**
|
||||
Returns all archived events. For test assertions.
|
||||
|
||||
**`clear(): void`**
|
||||
Empties both the main store and the archive. Use in `afterEach` test teardown.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The severity filter applies range semantics: when multiple severities are provided it matches events whose severity order falls between the minimum and maximum provided ordinals. This is intentional — it allows callers to request `[WARNING, ERROR, CRITICAL]` without listing every intermediate level. Events are sorted using `Array.sort()` which is stable in V8 / Bun, so tie-breaking by insertion order is reliable. The internal `archived` map is keyed by `location:id` to allow the same event to be archived to multiple locations in tests without collision.
|
||||
@@ -0,0 +1,101 @@
|
||||
# storage-adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the `AuditStorageAdapter` interface contract that all storage backends must implement. This adapter pattern decouples the `AuditLogger` from any specific persistence technology — in-memory, SQLite, SurrealDB, PostgreSQL, or S3 — allowing implementations to be swapped without changing logger code.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuditStorageAdapter` interface with six methods covering the full CRUD + lifecycle surface
|
||||
- `BaseAuditStorageAdapter` abstract class with typed abstract method signatures for concrete adapters
|
||||
- Supports pagination, time-range filtering, tenant scoping, and soft-archive semantics
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { AuditStorageAdapter } from "@suaveplan/audit-logging/adapters/storage";
|
||||
|
||||
class MyAdapter implements AuditStorageAdapter {
|
||||
async save(event) { /* ... */ }
|
||||
async query(options) { return []; }
|
||||
async getLatestEvent(tenantId?) { return null; }
|
||||
async deleteExpired(before) { return 0; }
|
||||
async archive(before, location) { return 0; }
|
||||
async count(options?) { return 0; }
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { BaseAuditStorageAdapter } from "@suaveplan/audit-logging/adapters/storage";
|
||||
import type { AuditEvent, AuditQueryOptions } from "@suaveplan/audit-logging";
|
||||
|
||||
class SqliteAuditAdapter extends BaseAuditStorageAdapter {
|
||||
constructor(private readonly db: DatabaseAdapter) {
|
||||
super();
|
||||
}
|
||||
|
||||
async save(event: AuditEvent): Promise<void> {
|
||||
await this.db.query("INSERT INTO audit_events VALUES (?)", [JSON.stringify(event)]);
|
||||
}
|
||||
|
||||
async query(options: AuditQueryOptions): Promise<AuditEvent[]> {
|
||||
// Build WHERE clause from options...
|
||||
return [];
|
||||
}
|
||||
|
||||
async getLatestEvent(tenantId?: string): Promise<AuditEvent | null> {
|
||||
// SELECT ... ORDER BY timestamp DESC LIMIT 1
|
||||
return null;
|
||||
}
|
||||
|
||||
async deleteExpired(before: Date): Promise<number> {
|
||||
const result = await this.db.query("DELETE FROM audit_events WHERE timestamp < ?", [before]);
|
||||
return result.rowsAffected ?? 0;
|
||||
}
|
||||
|
||||
async archive(before: Date, location: string): Promise<number> {
|
||||
// Copy to archive, then mark deleted
|
||||
return 0;
|
||||
}
|
||||
|
||||
async count(options?: Partial<AuditQueryOptions>): Promise<number> {
|
||||
const result = await this.db.query("SELECT COUNT(*) FROM audit_events");
|
||||
return Number(result.rows[0][0]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuditStorageAdapter` (interface)
|
||||
|
||||
**`save(event: AuditEvent): Promise<void>`**
|
||||
Persist a single audit event. Throws `AuditError` with `STORAGE_ERROR` on failure.
|
||||
|
||||
**`query(options: AuditQueryOptions): Promise<AuditEvent[]>`**
|
||||
Filter and paginate events. Applies all provided filters; returns an empty array when no events match.
|
||||
|
||||
**`getLatestEvent(tenantId?: string): Promise<AuditEvent | null>`**
|
||||
Returns the most recent event (by timestamp) for the given tenant, or globally if no tenant is specified. Used by `AuditLogger` to link HMAC chains.
|
||||
|
||||
**`deleteExpired(before: Date): Promise<number>`**
|
||||
Hard-deletes all events with `timestamp < before`. Returns the count of deleted records.
|
||||
|
||||
**`archive(before: Date, location: string): Promise<number>`**
|
||||
Copies events with `timestamp < before` to `location`, then marks them. Returns the count of archived records.
|
||||
|
||||
**`count(options?: Partial<AuditQueryOptions>): Promise<number>`**
|
||||
Returns the total number of events matching the optional filter criteria.
|
||||
|
||||
### `BaseAuditStorageAdapter` (abstract class)
|
||||
|
||||
Abstract base that implements `AuditStorageAdapter` with abstract method signatures. Extend this class when building new adapters to get compile-time enforcement that all required methods are implemented.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The interface is intentionally minimal — it does not prescribe transaction semantics, connection pooling, or retry logic, which are concerns of specific adapter implementations. The `archive` method is a two-phase operation; the exact archival format (JSON Lines, Parquet, CSV) is left to the implementation. The `getLatestEvent` method is called once per `log()` invocation when chain integrity is enabled, so implementations should ensure it is fast (indexed by timestamp).
|
||||
@@ -0,0 +1,127 @@
|
||||
# audit-logger
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The `AuditLogger` class is the primary entry point for the `@suaveplan/audit-logging` package. It orchestrates event creation (ID generation, timestamping, HMAC signing), storage persistence, GDPR data export, and retention policy enforcement. All dependencies — storage adapter, clock, ID generator, logger, and event bus — are injected through the constructor, enabling full testability without mocks.
|
||||
|
||||
## Features
|
||||
|
||||
- `log()` — generates ID and timestamp, computes HMAC hash (with optional chain linking), saves event, emits OTel counter and bus event
|
||||
- `query()` — delegates to the storage adapter with full filter support
|
||||
- `verifyChainIntegrity()` — delegates to `verifyChain()` in chain-integrity module
|
||||
- `exportGDPRData()` — collects all events for a user ID, builds summary statistics, respects AbortSignal
|
||||
- `applyRetentionPolicy()` — archives then hard-deletes events beyond the retention window
|
||||
- Injectable `Clock` and `IdGenerator` from `@suaveplan/types`
|
||||
- OTel counter `audit.events_logged_total` with `event_type` and `severity` labels
|
||||
- Bus event `audit:event:logged` emitted after each successful log
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { AuditLogger } from "@suaveplan/audit-logging/core/audit-logger/audit-logger";
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
import { AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
|
||||
const logger = new AuditLogger({
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: process.env.AUDIT_SECRET_KEY!,
|
||||
});
|
||||
|
||||
const event = await logger.log({
|
||||
type: AuditEventType.AUTH_LOGIN,
|
||||
severity: AuditSeverity.INFO,
|
||||
actor: { userId: "user-123", ipAddress: "203.0.113.1" },
|
||||
target: { type: "session", id: "sess-456" },
|
||||
});
|
||||
|
||||
console.log(event.id); // generated UUID
|
||||
console.log(event.hash); // hex HMAC-SHA256
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { AuditLogger } from "@suaveplan/audit-logging/core/audit-logger/audit-logger";
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
import { AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
|
||||
const adapter = new MemoryAuditAdapter();
|
||||
let seq = 0;
|
||||
|
||||
const logger = new AuditLogger({
|
||||
storage: adapter,
|
||||
secretKey: "my-secret",
|
||||
enableChainIntegrity: true,
|
||||
retentionPolicy: {
|
||||
retentionDays: 90,
|
||||
archiveBeforeDelete: true,
|
||||
archiveLocation: "s3://my-bucket/audit",
|
||||
},
|
||||
clock: () => Date.now(),
|
||||
idGenerator: () => `evt-${++seq}`,
|
||||
});
|
||||
|
||||
// Log a chain of events
|
||||
await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "u1" } });
|
||||
await logger.log({ type: AuditEventType.DATA_READ, severity: AuditSeverity.INFO, actor: { userId: "u1" }, target: { type: "report", id: "r-1" } });
|
||||
|
||||
// Verify chain
|
||||
const events = await logger.query({ order: "asc" });
|
||||
const result = await logger.verifyChainIntegrity(events);
|
||||
console.log(result.valid); // true
|
||||
|
||||
// GDPR export
|
||||
const gdprExport = await logger.exportGDPRData("u1");
|
||||
console.log(gdprExport.summary.totalEvents); // 2
|
||||
|
||||
// Apply retention
|
||||
const retention = await logger.applyRetentionPolicy();
|
||||
console.log(retention.eventsDeleted, retention.eventsArchived);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new AuditLogger(options: AuditLoggerOptions)`
|
||||
|
||||
Throws `AuditError` with `SECRET_KEY_MISSING` if `options.secretKey` is empty.
|
||||
|
||||
### `log(event): Promise<AuditEvent>`
|
||||
|
||||
Input type: `Omit<AuditEvent, 'id' | 'timestamp' | 'hash' | 'previousHash'>`.
|
||||
|
||||
- Generates `id` via `idGenerator()`
|
||||
- Sets `timestamp` via `new Date(clock())`
|
||||
- If `enableChainIntegrity`: fetches latest event hash as `previousHash`
|
||||
- Computes `hash = computeEventHash(event, secretKey, previousHash)`
|
||||
- Saves to storage; throws `AuditError(STORAGE_ERROR)` on failure
|
||||
- Increments OTel counter `audit.events_logged_total`
|
||||
- Emits `audit:event:logged` on bus
|
||||
|
||||
### `query(options): Promise<AuditEvent[]>`
|
||||
|
||||
Delegates to `storage.query(options)`. Throws `AuditError(STORAGE_ERROR)` on storage failure.
|
||||
|
||||
### `verifyChainIntegrity(events): Promise<ChainVerificationResult>`
|
||||
|
||||
Events must be in ascending chronological order. Delegates to `verifyChain`.
|
||||
|
||||
### `exportGDPRData(userId, signal?): Promise<GDPRDataExport>`
|
||||
|
||||
- Queries all events with `{ userId }`
|
||||
- Builds `eventTypes` frequency map and `dateRange`
|
||||
- Returns `GDPRDataExport` with summary
|
||||
- Throws `AuditError(EXPORT_FAILED)` on aborted signal or storage failure
|
||||
|
||||
### `applyRetentionPolicy(signal?): Promise<RetentionResult>`
|
||||
|
||||
- Requires `retentionPolicy` option to be set (returns empty result otherwise)
|
||||
- Archives events older than `retentionDays` if `archiveBeforeDelete && archiveLocation`
|
||||
- Hard-deletes expired events via `storage.deleteExpired(cutoff)`
|
||||
- Non-fatal errors are collected in `result.errors`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The counter and meter are created once at module scope to avoid per-instance SDK lookups and respect OTel's singleton meter provider model. The `bus.emit` call is fire-and-forget (no await) to keep the hot path synchronous. The `previousHash` lookup uses tenant scoping when `event.tenantId` is set, so multi-tenant deployments maintain separate chains per tenant. All optional properties on `AuditEvent` are included via spread with conditional key inclusion to satisfy `exactOptionalPropertyTypes`.
|
||||
@@ -0,0 +1,97 @@
|
||||
# chain-integrity
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides the cryptographic chain-integrity subsystem for `@suaveplan/audit-logging`. Each audit event is signed with an HMAC-SHA256 digest that covers both the event's canonical JSON form and the hash of the preceding event, forming a tamper-evident linked chain. Any post-hoc modification to an event — or insertion/deletion of events — will cause `verifyChain` to detect the broken link at the exact index.
|
||||
|
||||
## Features
|
||||
|
||||
- `canonicalise` — deterministic JSON serialisation with sorted keys for cross-runtime stability
|
||||
- `computeEventHash` — HMAC-SHA256 over `previousHash:canonicalJSON` using `@suaveplan/crypto-hash`
|
||||
- `verifyChain` — iterates an ordered event sequence, recomputes hashes, and returns pass/fail with broken-at index
|
||||
- No `node:crypto` usage — all cryptography via `@suaveplan/crypto-hash`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { computeEventHash, verifyChain } from "@suaveplan/audit-logging/core/chain-integrity/chain-integrity";
|
||||
|
||||
const secretKey = "super-secret-key";
|
||||
|
||||
const eventPayload = {
|
||||
id: "evt-1",
|
||||
type: "auth_login",
|
||||
severity: "info",
|
||||
actor: { userId: "user-1" },
|
||||
timestamp: new Date("2024-01-01T00:00:00Z"),
|
||||
};
|
||||
|
||||
const hash = computeEventHash(eventPayload, secretKey);
|
||||
const event = { ...eventPayload, hash };
|
||||
|
||||
const result = verifyChain([event], secretKey);
|
||||
console.log(result.valid); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { computeEventHash, verifyChain } from "@suaveplan/audit-logging/core/chain-integrity/chain-integrity";
|
||||
|
||||
const secretKey = "my-secret";
|
||||
const events = [];
|
||||
|
||||
// Build chain
|
||||
let previousHash: string | undefined;
|
||||
for (const payload of rawPayloads) {
|
||||
const hash = computeEventHash(payload, secretKey, previousHash);
|
||||
const event = { ...payload, hash, ...(previousHash ? { previousHash } : {}) };
|
||||
events.push(event);
|
||||
previousHash = hash;
|
||||
}
|
||||
|
||||
// Verify chain
|
||||
const result = verifyChain(events, secretKey);
|
||||
if (!result.valid) {
|
||||
console.error(`Chain broken at index ${result.brokenAt}: ${result.reason}`);
|
||||
}
|
||||
|
||||
// Tamper detection example
|
||||
const tampered = [...events];
|
||||
tampered[2] = { ...tampered[2], actor: { userId: "attacker" } };
|
||||
const tamperResult = verifyChain(tampered, secretKey);
|
||||
console.log(tamperResult.brokenAt); // 2
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `canonicalise(event): string`
|
||||
|
||||
Serialises an audit event to deterministic JSON with sorted keys, excluding `hash` and `previousHash`.
|
||||
|
||||
- `event` — event minus hash fields (all other fields serialised)
|
||||
- Returns: canonical JSON string
|
||||
|
||||
### `computeEventHash(event, secretKey, previousHash?): string`
|
||||
|
||||
Computes HMAC-SHA256 over the canonical representation.
|
||||
|
||||
- `event` — event without hash/previousHash
|
||||
- `secretKey` — UTF-8 signing key; must be non-empty
|
||||
- `previousHash?` — hex hash of preceding event; prepended as `${previousHash}:` before the canonical JSON
|
||||
- Returns: lowercase hex string (64 characters for SHA-256)
|
||||
|
||||
### `verifyChain(events, secretKey): ChainVerificationResult`
|
||||
|
||||
Verifies an ordered sequence of events.
|
||||
|
||||
- `events` — events in ascending chronological order
|
||||
- `secretKey` — the secret used when originally signing
|
||||
- Returns: `{ valid: true }` or `{ valid: false, brokenAt: number, reason: string }`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Key sort in `canonicalise` is recursive via `sortReplacer` to handle nested objects like `target.metadata`. The actor object is sorted separately before the main sort pass to avoid double-sorting overhead. HMAC uses SHA-256 (not SHA-512) for a good balance of security and performance given the expected volume of audit events. The chain model does not enforce sequential ordering by `previousHash` linkage — it trusts the caller to provide events in order and simply recomputes each event's hash to detect any tampering. This design keeps verification O(n) without requiring a full DAG traversal.
|
||||
@@ -0,0 +1,91 @@
|
||||
# audit-error
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides a stable error-code registry and typed error class for `@suaveplan/audit-logging`. All package-level failures are represented as `AuditError` instances carrying a stable `AUDIT.*` code, enabling programmatic error handling and downstream telemetry without relying on message string parsing.
|
||||
|
||||
## Features
|
||||
|
||||
- `AUDIT_CODES` — frozen registry of six stable error codes via `defineCodes`
|
||||
- `AuditErrorCode` — union type of all valid AUDIT code strings
|
||||
- `AuditError` — extends `SuaveplanError<AuditErrorCode>` for full cause-chain support, OTel span events, and redaction
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { AuditError, AUDIT_CODES } from "@suaveplan/audit-logging/errors";
|
||||
|
||||
throw new AuditError(
|
||||
AUDIT_CODES.STORAGE_ERROR,
|
||||
"audit.storage.save.failed",
|
||||
{ context: { table: "audit_events", operation: "insert" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { AuditError, AUDIT_CODES } from "@suaveplan/audit-logging/errors";
|
||||
import { walkCauses } from "@suaveplan/error";
|
||||
|
||||
try {
|
||||
await adapter.save(event);
|
||||
} catch (err) {
|
||||
throw new AuditError(
|
||||
AUDIT_CODES.STORAGE_ERROR,
|
||||
"audit.storage.save.failed",
|
||||
{
|
||||
...(err instanceof Error ? { cause: err } : {}),
|
||||
context: { eventId: event.id, eventType: event.type },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle downstream:
|
||||
try {
|
||||
await logger.log(auditEvent);
|
||||
} catch (err) {
|
||||
if (err instanceof AuditError && err.code === AUDIT_CODES.CHAIN_BROKEN) {
|
||||
console.error("Tamper detected:", err.context);
|
||||
}
|
||||
for (const cause of walkCauses(err)) {
|
||||
console.error(" caused by:", cause.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AUDIT_CODES`
|
||||
|
||||
Frozen object with the following keys:
|
||||
|
||||
- `STORAGE_ERROR` → `"AUDIT.STORAGE_ERROR"` — storage backend operation failed
|
||||
- `CHAIN_BROKEN` → `"AUDIT.CHAIN_BROKEN"` — HMAC chain verification failed
|
||||
- `INVALID_EVENT` → `"AUDIT.INVALID_EVENT"` — event data failed schema validation
|
||||
- `EXPORT_FAILED` → `"AUDIT.EXPORT_FAILED"` — GDPR data export failed
|
||||
- `RETENTION_FAILED` → `"AUDIT.RETENTION_FAILED"` — retention policy application failed
|
||||
- `SECRET_KEY_MISSING` → `"AUDIT.SECRET_KEY_MISSING"` — no secret key provided for HMAC
|
||||
|
||||
### `AuditErrorCode`
|
||||
|
||||
Type alias: `"AUDIT.STORAGE_ERROR" | "AUDIT.CHAIN_BROKEN" | "AUDIT.INVALID_EVENT" | "AUDIT.EXPORT_FAILED" | "AUDIT.RETENTION_FAILED" | "AUDIT.SECRET_KEY_MISSING"`
|
||||
|
||||
### `AuditError`
|
||||
|
||||
`class AuditError extends SuaveplanError<AuditErrorCode>`
|
||||
|
||||
**Constructor:**
|
||||
|
||||
- `code: AuditErrorCode` — stable error code from `AUDIT_CODES`
|
||||
- `messageKey: string` — i18n message key (resolved by the global message resolver)
|
||||
- `options?: SuaveplanErrorOptions` — optional `cause`, `context`, `clock`, `traceId`
|
||||
|
||||
**Inherited properties:** `code`, `context`, `timestamp`, `traceId`, `cause` (from `SuaveplanError`)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` is called once at module load time with a `const` array, so TypeScript infers the exact literal union for `AuditErrorCode`. Using `as const` on the reasons array is essential — without it the inference falls back to `string[]` and the code type widens to `string`. The `AuditError` constructor delegates entirely to `SuaveplanError`; its only addition is setting `this.name = "AuditError"` for cleaner stack traces and `instanceof` checks.
|
||||
@@ -0,0 +1,110 @@
|
||||
# audit-logging.schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides Zod v4 schema definitions for all public types in `@suaveplan/audit-logging`. These schemas enable runtime validation at API boundaries, wire deserialization, and property-based testing. Every schema mirrors its corresponding TypeScript interface exactly, ensuring type inference from `z.infer<>` produces the same shape as the hand-written types in `audit-logging.types.ts`.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuditSeveritySchema` — enum validator for the five severity levels
|
||||
- `AuditEventTypeSchema` — enum validator for all 29 built-in event types
|
||||
- `AuditActorSchema` — optional-field object schema for actors
|
||||
- `AuditTargetSchema` — required type/id with optional metadata record
|
||||
- `AuditEventSchema` — complete event record with all fields
|
||||
- `AuditQueryOptionsSchema` — query filter schema with union severity support
|
||||
- `AuditRetentionPolicySchema` — policy with positive retentionDays validation
|
||||
- `ChainVerificationResultSchema` — pass/fail result with optional broken-at index
|
||||
- `RetentionResultSchema` — counts and errors array
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { AuditEventSchema } from "@suaveplan/audit-logging/schemas/audit-logging.schemas";
|
||||
|
||||
const raw = JSON.parse(apiResponse);
|
||||
const event = AuditEventSchema.parse({
|
||||
...raw,
|
||||
timestamp: new Date(raw.timestamp),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
AuditQueryOptionsSchema,
|
||||
AuditRetentionPolicySchema,
|
||||
} from "@suaveplan/audit-logging/schemas/audit-logging.schemas";
|
||||
import { AuditLogger } from "@suaveplan/audit-logging/core/audit-logger/audit-logger";
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
|
||||
// Validate query options from user input
|
||||
function parseQueryOptions(input: unknown) {
|
||||
return AuditQueryOptionsSchema.parse(input);
|
||||
}
|
||||
|
||||
// Validate policy from config file
|
||||
function parsePolicy(input: unknown) {
|
||||
const policy = AuditRetentionPolicySchema.parse(input);
|
||||
return new AuditLogger({
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: "key",
|
||||
retentionPolicy: policy,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuditSeveritySchema`
|
||||
|
||||
`z.enum(["debug", "info", "warning", "error", "critical"])`
|
||||
|
||||
### `AuditEventTypeSchema`
|
||||
|
||||
`z.enum([...all 29 values...])`
|
||||
|
||||
### `AuditActorSchema`
|
||||
|
||||
All fields optional: `userId`, `sessionId`, `apiKeyId`, `ipAddress`, `userAgent`, `tenantId`.
|
||||
|
||||
### `AuditTargetSchema`
|
||||
|
||||
- `type: string` (required)
|
||||
- `id: string` (required)
|
||||
- `metadata?: Record<string, unknown>`
|
||||
|
||||
### `AuditEventSchema`
|
||||
|
||||
- All AuditEvent fields with proper Zod types
|
||||
- `timestamp: z.date()` — expects a `Date` object (not a string)
|
||||
- `type: z.union([AuditEventTypeSchema, z.string()])` — accepts custom types
|
||||
|
||||
### `AuditQueryOptionsSchema`
|
||||
|
||||
All fields optional. `severity` accepts a single value or array.
|
||||
|
||||
### `AuditRetentionPolicySchema`
|
||||
|
||||
- `retentionDays: z.number().int().positive()` — must be ≥ 1
|
||||
- `archiveBeforeDelete: z.boolean()`
|
||||
- `archiveLocation?: z.string()`
|
||||
|
||||
### `ChainVerificationResultSchema`
|
||||
|
||||
- `valid: z.boolean()`
|
||||
- `brokenAt?: z.number().int().nonnegative()`
|
||||
- `reason?: z.string()`
|
||||
|
||||
### `RetentionResultSchema`
|
||||
|
||||
- `eventsDeleted: z.number().int().nonnegative()`
|
||||
- `eventsArchived: z.number().int().nonnegative()`
|
||||
- `errors: z.array(z.string())`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Schemas use `z.record(z.string(), z.unknown())` (two-argument form) per Zod v4 requirements. The `AuditEventTypeSchema` enumerates all values from the `AuditEventType` enum directly rather than using `z.nativeEnum()`, which is deprecated in Zod v4 in favour of `z.enum()`. The `timestamp` field expects an actual `Date` object — callers parsing JSON should convert the ISO string first with `new Date(raw.timestamp)`.
|
||||
@@ -0,0 +1,148 @@
|
||||
# audit-logging.types
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the complete TypeScript type system for `@suaveplan/audit-logging`. These types form the public contract for all audit event data, query filtering, storage adapters, GDPR exports, and chain-integrity verification. Keeping them in a dedicated types file ensures zero runtime overhead — types are erased during compilation, and consumers can import only what they need.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuditActor` — who triggered an event (user, session, API key, IP, tenant)
|
||||
- `AuditTarget` — the resource acted upon (type, id, optional metadata)
|
||||
- `AuditEvent` — complete immutable event record including HMAC chain fields
|
||||
- `AuditQueryOptions` — rich filtering: event types, user/session, severity, time range, pagination
|
||||
- `AuditRetentionPolicy` — configurable TTL with archive-before-delete support
|
||||
- `AuditLoggerOptions` — injectable clock, ID generator, logger, event bus, and storage adapter
|
||||
- `AuditStorageAdapter` — pluggable backend interface (save, query, count, archive, deleteExpired)
|
||||
- `GDPRDataExport` — structured export with event list and summary statistics
|
||||
- `ChainVerificationResult` — pass/fail with broken-at index and reason string
|
||||
- `RetentionResult` — counts of deleted/archived events plus any errors
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { AuditEvent, AuditActor, AuditTarget } from "@suaveplan/audit-logging";
|
||||
import { AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
|
||||
const actor: AuditActor = {
|
||||
userId: "user-123",
|
||||
ipAddress: "203.0.113.1",
|
||||
};
|
||||
|
||||
const target: AuditTarget = {
|
||||
type: "document",
|
||||
id: "doc-456",
|
||||
};
|
||||
|
||||
const event: AuditEvent = {
|
||||
id: "evt-789",
|
||||
type: AuditEventType.DATA_READ,
|
||||
severity: AuditSeverity.INFO,
|
||||
actor,
|
||||
target,
|
||||
timestamp: new Date(),
|
||||
hash: "computed-hmac",
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { AuditQueryOptions, AuditRetentionPolicy, AuditLoggerOptions } from "@suaveplan/audit-logging";
|
||||
import { AuditEventType, AuditSeverity } from "@suaveplan/audit-logging";
|
||||
import { MemoryAuditAdapter } from "@suaveplan/audit-logging/adapters/memory/memory-adapter";
|
||||
|
||||
const query: AuditQueryOptions = {
|
||||
eventTypes: [AuditEventType.AUTH_LOGIN, AuditEventType.AUTH_LOGOUT],
|
||||
severity: [AuditSeverity.WARNING, AuditSeverity.ERROR, AuditSeverity.CRITICAL],
|
||||
startTime: new Date("2024-01-01"),
|
||||
endTime: new Date("2024-12-31"),
|
||||
tenantId: "tenant-abc",
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
order: "desc",
|
||||
};
|
||||
|
||||
const retention: AuditRetentionPolicy = {
|
||||
retentionDays: 90,
|
||||
archiveBeforeDelete: true,
|
||||
archiveLocation: "s3://my-bucket/audit-archive",
|
||||
};
|
||||
|
||||
const options: AuditLoggerOptions = {
|
||||
storage: new MemoryAuditAdapter(),
|
||||
secretKey: "my-secret-key",
|
||||
enableChainIntegrity: true,
|
||||
retentionPolicy: retention,
|
||||
clock: () => Date.now(),
|
||||
idGenerator: () => crypto.randomUUID(),
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuditActor`
|
||||
|
||||
All fields optional. Represents the entity that performed the action.
|
||||
|
||||
- `userId?: string` — authenticated user identifier
|
||||
- `sessionId?: string` — active session identifier
|
||||
- `apiKeyId?: string` — API key used for the request
|
||||
- `ipAddress?: string` — source IP address
|
||||
- `userAgent?: string` — HTTP User-Agent header
|
||||
- `tenantId?: string` — tenant in multi-tenant deployments
|
||||
|
||||
### `AuditTarget`
|
||||
|
||||
- `type: string` — resource type (e.g. `"document"`, `"user"`)
|
||||
- `id: string` — resource identifier
|
||||
- `metadata?: Record<string, unknown>` — additional context
|
||||
|
||||
### `AuditEvent`
|
||||
|
||||
- `id: string` — unique event identifier
|
||||
- `type: AuditEventType | string` — event type from the taxonomy or custom string
|
||||
- `severity: AuditSeverity` — debug/info/warning/error/critical
|
||||
- `actor: AuditActor` — who triggered the event
|
||||
- `target?: AuditTarget` — optional target resource
|
||||
- `details?: Record<string, unknown>` — arbitrary event-specific data
|
||||
- `timestamp: Date` — when the event occurred
|
||||
- `previousHash?: string` — HMAC of the preceding event (chain integrity)
|
||||
- `hash: string` — HMAC-SHA256 of this event's canonical form
|
||||
- `tenantId?: string` — tenant scoping
|
||||
- `correlationId?: string` — request/trace correlation
|
||||
|
||||
### `AuditQueryOptions`
|
||||
|
||||
Filtering fields for `AuditStorageAdapter.query()`. All optional.
|
||||
|
||||
### `AuditStorageAdapter`
|
||||
|
||||
- `save(event)` — persist a single event
|
||||
- `query(options)` — filter and paginate events
|
||||
- `getLatestEvent(tenantId?)` — most recent event for chain linking
|
||||
- `deleteExpired(before)` — remove old events; returns count deleted
|
||||
- `archive(before, location)` — copy then mark events; returns count archived
|
||||
- `count(options?)` — count matching events
|
||||
|
||||
### `GDPRDataExport`
|
||||
|
||||
- `userId: string` — the data subject
|
||||
- `exportedAt: Date` — export timestamp
|
||||
- `auditEvents: AuditEvent[]` — all events involving the user
|
||||
- `summary` — `totalEvents`, `eventTypes` map, `dateRange`
|
||||
|
||||
### `ChainVerificationResult`
|
||||
|
||||
- `valid: boolean` — whether chain is intact
|
||||
- `brokenAt?: number` — zero-based index of first broken link
|
||||
- `reason?: string` — human-readable explanation
|
||||
|
||||
### `RetentionResult`
|
||||
|
||||
- `eventsDeleted: number` — count of hard-deleted events
|
||||
- `eventsArchived: number` — count of archived events
|
||||
- `errors: string[]` — non-fatal errors encountered
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Types are defined as interfaces rather than classes to allow multiple implementations of `AuditStorageAdapter` without prototype overhead. The `exactOptionalPropertyTypes` constraint means all optional fields use `?:` and callers must use spread syntax to conditionally include them. The `AuditEvent.hash` field is always a hex-encoded HMAC-SHA256 string, computed by `computeEventHash` in `chain-integrity.ts`.
|
||||
Reference in New Issue
Block a user