# OpenSpec Change: enterprise-audit-logging - **Change ID**: enterprise-audit-logging - **Package**: `@suaveplan/audit-logging` - **Category**: universal - **Target path**: `universal/auth/audit-logging` - **Status**: PENDING - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/audit-logging.md` --- ## Summary Deliver `@suaveplan/audit-logging` on the genesis branch from scratch at enterprise quality. The package provides a tamper-proof, GDPR-compliant audit trail: `AuditLogger` with cryptographic chain-integrity signing, pluggable storage adapters, retention policy enforcement, GDPR data export, and a rich event taxonomy spanning auth, authorization, data access, and system events. The main-branch implementation was tightly coupled to SurrealDB and Node's `node:crypto`; the genesis rewrite is database-agnostic with a `StorageAdapter` interface, uses `@suaveplan/crypto` for HMAC signing, adds structured errors via `@suaveplan/error`, Zod schemas, an injectable clock and ID generator, OTel instrumentation, property-based tests, and mitata benchmarks. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `AuditError`; `defineCodes` for `AUDIT.*` error codes | | `@suaveplan/types` (`workspace:^`) | `Clock` (injectable timestamps), `IdGenerator` (event IDs), `LoggerLike`, `StorageLike` | | `@suaveplan/events` (`workspace:^`) | `EventBusLike` for audit lifecycle events | | `@suaveplan/crypto` (`workspace:^`) | HMAC-SHA256 for chain-integrity signing, `zeroize` for secret clearing | | `@suaveplan/logger` (`workspace:^`) | Structured logging with trace correlation | | `@suaveplan/serialize` (`workspace:^`) | Deterministic JSON serialization for HMAC signing stability | | `zod` (`catalog:forms`) | Zod schemas for all public option types and event shapes | | `@opentelemetry/api` (`catalog:observability`) | Spans, counters, histograms | | `tslib` (`catalog:build`) | TypeScript runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`), `@suaveplan/testing` (`workspace:^`). --- ## Preserved public API surface (from main branch) ### Event taxonomy enums ```ts enum AuditEventType { // 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 } enum AuditSeverity { DEBUG = "debug", INFO = "info", WARNING = "warning", ERROR = "error", CRITICAL = "critical" } ``` ### Core types ```ts interface AuditActor { userId?: string sessionId?: string apiKeyId?: string ipAddress?: string userAgent?: string tenantId?: string } interface AuditTarget { type: string id: string metadata?: Record } interface AuditEvent { id: string type: AuditEventType | string severity: AuditSeverity actor: AuditActor target?: AuditTarget details?: Record timestamp: Date previousHash?: string // chain-integrity: hash of the previous event hash: string // HMAC-SHA256 of this event's canonical form tenantId?: string correlationId?: string } interface AuditLoggerOptions { storage: AuditStorageAdapter secretKey: string tableName?: string retentionPolicy?: AuditRetentionPolicy enableChainIntegrity?: boolean clock?: Clock idGenerator?: IdGenerator logger?: LoggerLike bus?: EventBusLike } interface AuditQueryOptions { eventTypes?: AuditEventType[] userId?: string sessionId?: string severity?: AuditSeverity | AuditSeverity[] targetType?: string targetId?: string startTime?: Date endTime?: Date limit?: number offset?: number order?: "asc" | "desc" tenantId?: string } interface AuditRetentionPolicy { retentionDays: number archiveBeforeDelete: boolean archiveLocation?: string } interface GDPRDataExport { userId: string exportedAt: Date auditEvents: AuditEvent[] summary: { totalEvents: number eventTypes: Record dateRange: { first: Date; last: Date } } } ``` ### AuditLogger class ```ts class AuditLogger { constructor(options: AuditLoggerOptions) log(event: Omit): Promise query(options: AuditQueryOptions): Promise verifyChainIntegrity(events: AuditEvent[]): Promise exportGDPRData(userId: string, signal?: AbortSignal): Promise applyRetentionPolicy(signal?: AbortSignal): Promise } interface ChainVerificationResult { valid: boolean brokenAt?: number // index in the events array where chain breaks reason?: string } interface RetentionResult { eventsDeleted: number eventsArchived: number errors: string[] } ``` ### Storage adapter interface ```ts interface AuditStorageAdapter { save(event: AuditEvent): Promise query(options: AuditQueryOptions): Promise getLatestEvent(tenantId?: string): Promise deleteExpired(before: Date): Promise archive(before: Date, location: string): Promise count(options?: Partial): Promise } // In-memory adapter for tests and single-node use class MemoryAuditAdapter implements AuditStorageAdapter ``` ### Error codes ```ts type AuditErrorCode = | "AUDIT_STORAGE_ERROR" | "AUDIT_CHAIN_BROKEN" | "AUDIT_INVALID_EVENT" | "AUDIT_EXPORT_FAILED" | "AUDIT_RETENTION_FAILED" | "AUDIT_SECRET_KEY_MISSING" class AuditError extends SuaveplanError function registerAuditErrorCodes(): void ``` --- ## New additions for genesis ### 1. Database-agnostic `AuditStorageAdapter` Replaces the hard SurrealDB coupling. `MemoryAuditAdapter` ships in-package. Database-specific adapters (SurrealDB, PostgreSQL, SQLite) live in separate packages. ### 2. Chain integrity via `@suaveplan/crypto` HMAC-SHA256 signing uses `@suaveplan/crypto`'s `hmac` (delegates to `@suaveplan/crypto-hash`) instead of `node:crypto`. The secret key is `zeroize`d on `AuditLogger` disposal. Canonical JSON serialization for HMAC input uses `@suaveplan/serialize`'s deterministic encoder. ### 3. Clock and IdGenerator injection `AuditLogger` accepts `clock?: Clock` and `idGenerator?: IdGenerator`. All `Date.now()` and `randomUUID()` calls delegated to injected instances so audit events have deterministic timestamps and IDs in tests. ### 4. AbortSignal on long-running operations `exportGDPRData` and `applyRetentionPolicy` accept an optional `AbortSignal`; abort mid-iteration throws `AuditError` with `AUDIT_EXPORT_FAILED` or `AUDIT_RETENTION_FAILED`. ### 5. OTel instrumentation - `audit.log` span + `audit.events_logged_total` counter (label `event_type`, `severity`) - `audit.query` span + histogram `audit.query_duration_ms` - `audit.chain_verify` span + `audit.chain_verify_total` counter (label `valid: bool`) - `audit.gdpr_export` span + `audit.gdpr_export_total` counter - `audit.retention_apply` span + `audit.events_deleted_total` / `audit.events_archived_total` counters ### 6. Zod schemas `AuditEventSchema`, `AuditActorSchema`, `AuditTargetSchema`, `AuditQueryOptionsSchema`, `AuditLoggerOptionsSchema`, `AuditRetentionPolicySchema`, `GDPRDataExportSchema`. ### 7. EventBus integration Optional `bus?: EventBusLike` on `AuditLoggerOptions`. Emits: | Event | Payload | |---|---| | `audit:event:logged` | `{ id, type, severity, tenantId }` | | `audit:chain:broken` | `{ brokenAt, reason }` | | `audit:retention:applied` | `{ deleted, archived }` | ### 8. Property-based tests (fast-check) - HMAC stability: same event data always produces the same hash regardless of object property order. - Chain integrity: any mutation of any event in a sequence breaks verification at the mutated index. - Query filter correctness: events returned by `query({ userId: X })` all have `actor.userId === X`. - Retention idempotency: applying retention policy twice with the same cutoff date deletes zero additional events on the second run. ### 9. Benchmark suite - `AuditLogger.log` — 10 000 iterations with chain-integrity enabled vs disabled. - `AuditLogger.query` — 10 000 queries on a MemoryAuditAdapter with 10 000 pre-loaded events. - `verifyChainIntegrity` — on 100, 1 000, 10 000 event chains. --- ## File layout ```text universal/auth/audit-logging/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts # AuditEventType, AuditSeverity types/ audit-logging.types.ts audit-logging.types.md adapters/ storage-adapter/ storage-adapter.ts # AuditStorageAdapter interface storage-adapter.md storage-adapter.types.ts index.ts memory-adapter/ memory-adapter.ts memory-adapter.md memory-adapter.test.ts index.ts core/ audit-logger/ audit-logger.ts # AuditLogger class audit-logger.md audit-logger.types.ts audit-logger.test.ts index.ts chain-integrity/ chain-integrity.ts # computeHash, verifyChain chain-integrity.md chain-integrity.test.ts index.ts gdpr/ gdpr-export.ts # exportGDPRData logic gdpr-export.md gdpr-export.test.ts index.ts retention/ retention-policy.ts # applyRetentionPolicy logic retention-policy.md retention-policy.test.ts index.ts schemas/ audit-logging.schemas.ts audit-logging.schemas.md audit-logging.schemas.test.ts __benchmarks__/ audit-logging.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream packages**: auth-related packages and API gateways will write audit events. The storage adapter pattern allows a SurrealDB adapter to be shipped separately. **Breaking changes from main-branch API**: `AuditLoggerOptions.database` (SurrealDB) is replaced by `AuditLoggerOptions.storage` (adapter interface). All other exported symbols are preserved. No `node:crypto` import in the public interface. **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/crypto`, `@suaveplan/logger`, `@suaveplan/serialize` must be published to Verdaccio before this package can be built or tested.