Audit - rotalabs-comply
Description: AI compliance and audit logging infrastructure

Skip to content

# Audit Module

Audit logging infrastructure including the main logger, encryption utilities, and storage backends.

## AuditLogger

Main audit logging interface for AI compliance.

The AuditLogger provides a high-level interface for recording AI interactions, including inputs, outputs, safety evaluations, and performance metrics. It supports optional encryption of content and configurable retention policies.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`storage` | `Union[StorageBackend, str]` | Either a StorageBackend instance or a file path string. If a string is provided, FileStorage is automatically created. | required
`encryption` | `EncryptionManager | None` | Optional EncryptionManager for encrypting stored content. | `None`
`store_content` | `bool` | If True, stores actual content. If False, only stores content hashes for privacy (default: False). | `False`
`retention_days` | `int` | Number of days to retain entries before cleanup (default: 365). | `365`

Attributes:

Name | Type | Description
--- | --- | ---
`storage` | `StorageBackend` | The underlying storage backend.
`encryption` |  | The encryption manager (if provided).
`store_content` |  | Whether to store actual content.
`retention_days` |  | Retention period in days.

Example

Basic usage with file storage:

> > > logger = AuditLogger("/var/log/audit") entry_id = await logger.log( ... input="What is 2+2?", ... output="4", ... provider="openai", ... model="gpt-4", ... )

With encryption and content storage:

> > > from rotalabs_comply.audit import EncryptionManager encryption = EncryptionManager() logger = AuditLogger( ... "/var/log/audit", ... encryption=encryption, ... store_content=True, ... ) entry_id = await logger.log( ... input="Sensitive question", ... output="Sensitive answer", ... safety_passed=True, ... )

With custom storage backend:

> > > from rotalabs_comply.audit import MemoryStorage storage = MemoryStorage(max_entries=10000) logger = AuditLogger(storage)

## `__init__(storage, encryption=None, store_content=False, retention_days=365)`

Initialize the audit logger.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`storage` | `Union[StorageBackend, str]` | StorageBackend instance or file path string. | required
`encryption` | `EncryptionManager | None` | Optional encryption manager for content encryption. | `None`
`store_content` | `bool` | Whether to store actual content (default: False). | `False`
`retention_days` | `int` | Days to retain entries (default: 365). | `365`

## `cleanup_expired()` `async`

Delete entries older than the retention period.

Removes all entries with timestamps older than `retention_days` from the current time.

Returns:

Name | Type | Description
--- | --- | ---
`int` | `int` | Number of entries deleted.

Example

> > > ### Delete entries older than retention_days

> > > deleted_count = await logger.cleanup_expired() print(f"Cleaned up {deleted_count} expired entries")

Note

This operation may be slow for large datasets. Consider running during off-peak hours or using storage-native lifecycle policies (e.g., S3 lifecycle rules) for better performance.

## `decrypt_content(encrypted_content)`

Decrypt encrypted content from an audit entry.

Convenience method for decrypting content stored in audit entries when encryption is enabled.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`encrypted_content` | `str` | The encrypted content string from an AuditEntry. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The decrypted original content.

Raises:

Type | Description
--- | ---
`ValueError` | If no encryption manager is configured.
`InvalidToken` | If decryption fails.

Example

> > > entry = await logger.get_entry("abc-123") if entry and entry.input_content: ... original_input = logger.decrypt_content(entry.input_content)

## `get_entries(start, end)` `async`

Retrieve all audit entries within a time range.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`start` | `datetime` | Start of the time range (inclusive). | required
`end` | `datetime` | End of the time range (inclusive). | required

Returns:

Type | Description
--- | ---
`List[AuditEntry]` | List[AuditEntry]: All entries within the specified time range.

Example

> > > from datetime import datetime, timedelta end = datetime.utcnow() start = end - timedelta(days=7) entries = await logger.get_entries(start, end) print(f"Found {len(entries)} entries in the last week")

## `get_entry(entry_id)` `async`

Retrieve an audit entry by ID.

If encryption is enabled and store_content is True, the content will still be encrypted in the returned entry. Use the encryption manager to decrypt if needed.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry. | required

Returns:

Type | Description
--- | ---
`AuditEntry | None` | AuditEntry | None: The entry if found, None otherwise.

Example

> > > entry = await logger.get_entry("abc-123-def") if entry: ... print(f"Safety passed: {entry.safety_passed}")

## `log(input, output, provider=None, model=None, conversation_id=None, safety_passed=True, detectors_triggered=None, block_reason=None, alerts=None, latency_ms=0.0, input_tokens=None, output_tokens=None, metadata=None)` `async`

Log an AI interaction.

Creates an audit entry for the interaction and stores it in the configured storage backend.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`input` | `str` | The user input or prompt. | required
`output` | `str` | The AI-generated output or response. | required
`provider` | `str | None` | The AI provider (e.g., "openai", "anthropic"). | `None`
`model` | `str | None` | The model identifier (e.g., "gpt-4", "claude-3-opus"). | `None`
`conversation_id` | `str | None` | Optional ID to link related interactions. | `None`
`safety_passed` | `bool` | Whether the interaction passed safety checks (default: True). | `True`
`detectors_triggered` | `List[str] | None` | List of safety detector names that triggered. | `None`
`block_reason` | `str | None` | Reason for blocking, if the request was blocked. | `None`
`alerts` | `List[str] | None` | List of alert messages generated. | `None`
`latency_ms` | `float` | Time taken to process the request in milliseconds (default: 0.0). | `0.0`
`input_tokens` | `int | None` | Number of tokens in the input. | `None`
`output_tokens` | `int | None` | Number of tokens in the output. | `None`
`metadata` | `Dict[str, Any] | None` | Additional custom metadata dictionary. | `None`

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The unique entry ID for this audit log entry.

Example

> > > entry_id = await logger.log( ... input="Tell me a joke", ... output="Why did the chicken...", ... provider="anthropic", ... model="claude-3-opus", ... safety_passed=True, ... latency_ms=250.5, ... input_tokens=5, ... output_tokens=20, ... metadata={"session_id": "abc123"}, ... )

Main audit logging interface for AI compliance.

### Constructor

    AuditLogger(
        storage: Union[StorageBackend, str],
        encryption: Optional[EncryptionManager] = None,
        store_content: bool = False,
        retention_days: int = 365,
    )

Parameters:

Parameter | Type | Default | Description
--- | --- | --- | ---
`storage` | `Union[StorageBackend, str]` | Required | Storage backend or file path
`encryption` | `Optional[EncryptionManager]` | `None` | Encryption manager for content
`store_content` | `bool` | `False` | Store actual content vs hashes
`retention_days` | `int` | `365` | Days to retain entries

### Methods

#### log

    async def log(
        input: str,
        output: str,
        provider: Optional[str] = None,
        model: Optional[str] = None,
        conversation_id: Optional[str] = None,
        safety_passed: bool = True,
        detectors_triggered: Optional[List[str]] = None,
        block_reason: Optional[str] = None,
        alerts: Optional[List[str]] = None,
        latency_ms: float = 0.0,
        input_tokens: Optional[int] = None,
        output_tokens: Optional[int] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> str

Log an AI interaction and return the entry ID.

Example:

    entry_id = await logger.log(
        input="Tell me a joke",
        output="Why did the chicken...",
        provider="anthropic",
        model="claude-3-opus",
        safety_passed=True,
        latency_ms=250.5,
        metadata={"session_id": "abc123"},
    )

#### get_entry

    async def get_entry(entry_id: str) -> Optional[AuditEntry]

Retrieve an audit entry by ID.

Example:

    entry = await logger.get_entry("abc-123-def")
    if entry:
        print(f"Safety passed: {entry.safety_passed}")

#### get_entries

    async def get_entries(start: datetime, end: datetime) -> List[AuditEntry]

Retrieve all entries within a time range.

Example:

    from datetime import datetime, timedelta

    end = datetime.utcnow()
    start = end - timedelta(days=7)
    entries = await logger.get_entries(start, end)

#### cleanup_expired

    async def cleanup_expired() -> int

Delete entries older than the retention period. Returns count of deleted entries.

Example:

    deleted = await logger.cleanup_expired()
    print(f"Cleaned up {deleted} expired entries")

#### decrypt_content

    def decrypt_content(encrypted_content: str) -> str

Decrypt encrypted content from an audit entry.

Raises:

- `ValueError`: If no encryption manager is configured
- `cryptography.fernet.InvalidToken`: If decryption fails

Example:

    entry = await logger.get_entry("abc-123")
    if entry and entry.input_content:
        original = logger.decrypt_content(entry.input_content)

## Encryption

### EncryptionManager

High-level encryption manager for string data.

Provides a convenient interface for encrypting and decrypting string data, with automatic key generation if not provided.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`key` | `bytes | None` | Optional Fernet encryption key. If not provided, a new key is generated. | `None`

Attributes:

Name | Type | Description
--- | --- | ---
`_key` |  | The encryption key (kept private).
`_fernet` |  | The Fernet cipher instance.

Example

> > > manager = EncryptionManager() encrypted = manager.encrypt("sensitive data") manager.decrypt(encrypted) 'sensitive data'

> > > ## Use existing key

> > > key = generate_key() manager = EncryptionManager(key) manager.get_key() == key True

## `__init__(key=None)`

Initialize the encryption manager.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`key` | `bytes | None` | Optional Fernet encryption key. If None, generates a new key. | `None`

## `decrypt(data)`

Decrypt a base64-encoded encrypted string.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`data` | `str` | The base64-encoded encrypted string (from encrypt()). | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The original decrypted string.

Raises:

Type | Description
--- | ---
`InvalidToken` | If decryption fails.

Example

> > > manager = EncryptionManager() encrypted = manager.encrypt("secret") manager.decrypt(encrypted) 'secret'

## `encrypt(data)`

Encrypt a string and return base64-encoded result.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`data` | `str` | The string to encrypt. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | Base64-encoded encrypted string, safe for storage in JSON.

Example

> > > manager = EncryptionManager() encrypted = manager.encrypt("secret") isinstance(encrypted, str) True

## `get_key()`

Get the encryption key.

Returns:

Name | Type | Description
--- | --- | ---
`bytes` | `bytes` | The Fernet encryption key. Store this securely!

Warning

The encryption key must be stored securely. If lost, encrypted data cannot be recovered.

Example

> > > manager = EncryptionManager() key = manager.get_key() len(key) 44

High-level encryption manager for string data.

### Constructor

    EncryptionManager(key: Optional[bytes] = None)

Parameters:

Parameter | Type | Default | Description
--- | --- | --- | ---
`key` | `Optional[bytes]` | Auto-gen | Fernet encryption key

### Methods

#### encrypt

    def encrypt(data: str) -> str

Encrypt a string and return base64-encoded result.

#### decrypt

    def decrypt(data: str) -> str

Decrypt a base64-encoded encrypted string.

#### get_key

    def get_key() -> bytes

Get the encryption key. Store this securely!

Example:

    from rotalabs_comply import EncryptionManager

    manager = EncryptionManager()
    encrypted = manager.encrypt("sensitive data")
    decrypted = manager.decrypt(encrypted)

    # Save key securely
    key = manager.get_key()

### Helper Functions

#### generate_key

    def generate_key() -> bytes

Generate a new Fernet encryption key.

Example:

    from rotalabs_comply import generate_key

    key = generate_key()
    print(len(key))  # 44

#### encrypt

    def encrypt(data: bytes, key: bytes) -> bytes

Encrypt raw bytes using Fernet symmetric encryption.

#### decrypt

    def decrypt(data: bytes, key: bytes) -> bytes

Decrypt data that was encrypted with Fernet.

#### hash_content

    def hash_content(content: str) -> str

Compute SHA-256 hash of string content.

Example:

    from rotalabs_comply import hash_content

    content_hash = hash_content("hello world")
    # Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'

## Storage Backends

### StorageBackend Protocol

Protocol defining the interface for audit log storage backends.

All storage backends must implement these async methods to support writing, reading, listing, deleting, and counting audit entries.

## `count()` `abstractmethod` `async`

Count total number of entries in storage.

Returns:

Name | Type | Description
--- | --- | ---
`int` | `int` | Total number of stored entries.

## `delete(entry_id)` `abstractmethod` `async`

Delete an entry by ID.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry to delete. | required

Returns:

Name | Type | Description
--- | --- | ---
`bool` | `bool` | True if the entry was deleted, False if not found.

## `list_entries(start, end)` `abstractmethod` `async`

List all entries within a time range.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`start` | `datetime` | Start of the time range (inclusive). | required
`end` | `datetime` | End of the time range (inclusive). | required

Returns:

Type | Description
--- | ---
`List[AuditEntry]` | List[AuditEntry]: Entries within the specified time range.

## `read(entry_id)` `abstractmethod` `async`

Read an audit entry by ID.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry. | required

Returns:

Type | Description
--- | ---
`AuditEntry | None` | AuditEntry | None: The entry if found, None otherwise.

## `write(entry)` `abstractmethod` `async`

Write an audit entry to storage.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry` | `AuditEntry` | The audit entry to store. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The entry ID (same as entry.id).

Protocol defining the interface for audit log storage backends.

Required Methods:

Method | Signature | Description
--- | --- | ---
`write` | `async (entry: AuditEntry) -> str` | Write entry, return ID
`read` | `async (entry_id: str) -> Optional[AuditEntry]` | Read entry by ID
`list_entries` | `async (start: datetime, end: datetime) -> List[AuditEntry]` | List entries in range
`delete` | `async (entry_id: str) -> bool` | Delete entry, return success
`count` | `async () -> int` | Count total entries

### FileStorage

File-based storage backend using JSONL format.

Stores audit entries as JSON Lines files with automatic rotation when files exceed the configured size limit.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`path` | `str` | Directory path for storing audit files. | required
`rotation_size_mb` | `int` | Maximum file size in MB before rotation (default: 100). | `100`

Attributes:

Name | Type | Description
--- | --- | ---
`path` |  | The storage directory path.
`rotation_size_bytes` |  | Maximum file size in bytes.

Example

> > > storage = FileStorage("/var/log/audit") entry_id = await storage.write(entry) retrieved = await storage.read(entry_id)

## `__init__(path, rotation_size_mb=100)`

Initialize file storage.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`path` | `str` | Directory path for storing audit files. | required
`rotation_size_mb` | `int` | Maximum file size in MB before rotation. | `100`

## `count()` `async`

Count total number of entries in storage.

Returns:

Name | Type | Description
--- | --- | ---
`int` | `int` | Total number of stored entries.

## `delete(entry_id)` `async`

Delete an entry by ID.

Note: This rewrites the file without the deleted entry, which may be slow for large files. Consider using retention policies instead.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry to delete. | required

Returns:

Name | Type | Description
--- | --- | ---
`bool` | `bool` | True if the entry was deleted, False if not found.

## `list_entries(start, end)` `async`

List all entries within a time range.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`start` | `datetime` | Start of the time range (inclusive). | required
`end` | `datetime` | End of the time range (inclusive). | required

Returns:

Type | Description
--- | ---
`List[AuditEntry]` | List[AuditEntry]: Entries within the specified time range.

## `read(entry_id)` `async`

Read an audit entry by ID.

Searches through all JSONL files if the entry is not in the index.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry. | required

Returns:

Type | Description
--- | ---
`AuditEntry | None` | AuditEntry | None: The entry if found, None otherwise.

## `write(entry)` `async`

Write an audit entry to a JSONL file.

Auto-rotates the file if it exceeds the configured size limit.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry` | `AuditEntry` | The audit entry to store. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The entry ID.

File-based storage backend using JSONL format.

### Constructor

    FileStorage(path: str, rotation_size_mb: int = 100)

Parameters:

Parameter | Type | Default | Description
--- | --- | --- | ---
`path` | `str` | Required | Directory for audit files
`rotation_size_mb` | `int` | `100` | Max file size before rotation

File Structure:

    {path}/
    ├── audit_20260128.jsonl
    ├── audit_20260128_001.jsonl  # Rotated
    ├── audit_20260129.jsonl
    └── ...

Example:

    from rotalabs_comply.audit import FileStorage

    storage = FileStorage("/var/log/audit", rotation_size_mb=50)
    entry_id = await storage.write(entry)

### MemoryStorage

In-memory storage backend for testing and development.

Stores entries in a dictionary. Data is lost when the process ends.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`max_entries` | `int | None` | Optional maximum number of entries to store. When exceeded, oldest entries are removed. | `None`

Example

> > > storage = MemoryStorage(max_entries=1000) entry_id = await storage.write(entry) count = await storage.count()

## `__init__(max_entries=None)`

Initialize memory storage.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`max_entries` | `int | None` | Optional maximum number of entries to store. | `None`

## `count()` `async`

Count total number of entries in storage.

Returns:

Name | Type | Description
--- | --- | ---
`int` | `int` | Total number of stored entries.

## `delete(entry_id)` `async`

Delete an entry by ID.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry to delete. | required

Returns:

Name | Type | Description
--- | --- | ---
`bool` | `bool` | True if the entry was deleted, False if not found.

## `list_entries(start, end)` `async`

List all entries within a time range.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`start` | `datetime` | Start of the time range (inclusive). | required
`end` | `datetime` | End of the time range (inclusive). | required

Returns:

Type | Description
--- | ---
`List[AuditEntry]` | List[AuditEntry]: Entries within the specified time range.

## `read(entry_id)` `async`

Read an audit entry by ID.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry. | required

Returns:

Type | Description
--- | ---
`AuditEntry | None` | AuditEntry | None: The entry if found, None otherwise.

## `write(entry)` `async`

Write an audit entry to memory.

If max_entries is set and exceeded, removes the oldest entry.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry` | `AuditEntry` | The audit entry to store. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The entry ID.

In-memory storage backend for testing and development.

### Constructor

    MemoryStorage(max_entries: Optional[int] = None)

Parameters:

Parameter | Type | Default | Description
--- | --- | --- | ---
`max_entries` | `Optional[int]` | `None` | Max entries (LRU eviction)

Example:

    from rotalabs_comply.audit import MemoryStorage

    storage = MemoryStorage(max_entries=1000)
    count = await storage.count()

Data Persistence

Data is lost when the process ends. Use only for testing.

### S3Storage

AWS S3 storage backend for audit logs.

Stores each audit entry as a separate JSON file in S3, organized by date for easy querying and lifecycle management.

Requires boto3 to be installed (optional dependency).

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`bucket` | `str` | S3 bucket name. | required
`prefix` | `str` | Key prefix for audit files (default: "audit/"). | `'audit/'`
`region` | `str | None` | AWS region (optional, uses default if not specified). | `None`

File structure

{prefix}{YYYY-MM-DD}/{entry_id}.json

Example

> > > storage = S3Storage("my-audit-bucket", prefix="logs/audit/") entry_id = await storage.write(entry)

## Stored at: s3://my-audit-bucket/logs/audit/2024-01-15/abc123.json

## `__init__(bucket, prefix='audit/', region=None)`

Initialize S3 storage.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`bucket` | `str` | S3 bucket name. | required
`prefix` | `str` | Key prefix for audit files. | `'audit/'`
`region` | `str | None` | AWS region (optional). | `None`

## `count()` `async`

Count total number of entries in storage.

Returns:

Name | Type | Description
--- | --- | ---
`int` | `int` | Total number of stored entries.

## `delete(entry_id)` `async`

Delete an entry by ID.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry to delete. | required

Returns:

Name | Type | Description
--- | --- | ---
`bool` | `bool` | True if the entry was deleted, False if not found.

## `list_entries(start, end)` `async`

List all entries within a time range.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`start` | `datetime` | Start of the time range (inclusive). | required
`end` | `datetime` | End of the time range (inclusive). | required

Returns:

Type | Description
--- | ---
`List[AuditEntry]` | List[AuditEntry]: Entries within the specified time range.

## `read(entry_id)` `async`

Read an audit entry by ID.

Note: This searches through date prefixes which may be slow. Consider maintaining an index for production use.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry_id` | `str` | The unique identifier of the entry. | required

Returns:

Type | Description
--- | ---
`AuditEntry | None` | AuditEntry | None: The entry if found, None otherwise.

## `write(entry)` `async`

Write an audit entry to S3.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`entry` | `AuditEntry` | The audit entry to store. | required

Returns:

Name | Type | Description
--- | --- | ---
`str` | `str` | The entry ID.

AWS S3 storage backend for audit logs.

### Constructor

    S3Storage(
        bucket: str,
        prefix: str = "audit/",
        region: Optional[str] = None,
    )

Parameters:

Parameter | Type | Default | Description
--- | --- | --- | ---
`bucket` | `str` | Required | S3 bucket name
`prefix` | `str` | `"audit/"` | Key prefix for files
`region` | `Optional[str]` | `None` | AWS region

Key Structure:

    s3://{bucket}/{prefix}{YYYY-MM-DD}/{entry_id}.json

Example:

    from rotalabs_comply.audit import S3Storage

    storage = S3Storage(
        bucket="my-audit-bucket",
        prefix="prod/audit/",
        region="us-west-2",
    )

Dependency

Requires `boto3`. Install with `pip install rotalabs-comply[s3]`.

## AuditEntry (Storage)

Represents a single audit log entry.

Captures all relevant information about an AI interaction including inputs, outputs, safety evaluations, and performance metrics.

Attributes:

Name | Type | Description
--- | --- | ---
`id` | `str` | Unique identifier for this entry.
`timestamp` | `str` | When the interaction occurred (ISO format).
`input_hash` | `str` | SHA-256 hash of the input content.
`output_hash` | `str` | SHA-256 hash of the output content.
`input_content` | `str | None` | Actual input content (if store_content=True, may be encrypted).
`output_content` | `str | None` | Actual output content (if store_content=True, may be encrypted).
`provider` | `str | None` | The AI provider (e.g., "openai", "anthropic").
`model` | `str | None` | The model identifier (e.g., "gpt-4", "claude-3-opus").
`conversation_id` | `str | None` | Optional ID linking related interactions.
`safety_passed` | `bool` | Whether the interaction passed all safety checks.
`detectors_triggered` | `List[str]` | List of safety detector names that triggered.
`block_reason` | `str | None` | Reason for blocking, if the request was blocked.
`alerts` | `List[str]` | List of alert messages generated.
`latency_ms` | `float` | Time taken to process the request in milliseconds.
`input_tokens` | `int | None` | Number of tokens in the input.
`output_tokens` | `int | None` | Number of tokens in the output.
`metadata` | `Dict[str, Any]` | Additional custom metadata.

## `from_dict(data)` `classmethod`

Create entry from dictionary.

## `to_dict()`

Convert entry to dictionary for serialization.

Dataclass representing a single audit log entry in storage.

Attributes:

Attribute | Type | Description
--- | --- | ---
`id` | `str` | Unique identifier
`timestamp` | `str` | ISO format timestamp
`input_hash` | `str` | SHA-256 of input
`output_hash` | `str` | SHA-256 of output
`input_content` | `Optional[str]` | Input content (if stored)
`output_content` | `Optional[str]` | Output content (if stored)
`provider` | `Optional[str]` | AI provider
`model` | `Optional[str]` | Model identifier
`conversation_id` | `Optional[str]` | Conversation ID
`safety_passed` | `bool` | Safety check result
`detectors_triggered` | `List[str]` | Triggered detectors
`block_reason` | `Optional[str]` | Block reason
`alerts` | `List[str]` | Alert messages
`latency_ms` | `float` | Response latency
`input_tokens` | `Optional[int]` | Input token count
`output_tokens` | `Optional[int]` | Output token count
`metadata` | `Dict[str, Any]` | Custom metadata

Methods:

Method | Description
--- | ---
`to_dict()` | Convert to dictionary
`from_dict(data)` | Create from dictionary

## Helper Functions

### create_entry_id

    def create_entry_id() -> str

Generate a unique entry ID (UUID v4).

Example:

    from rotalabs_comply.audit.storage import create_entry_id

    entry_id = create_entry_id()
    # Returns: "550e8400-e29b-41d4-a716-446655440000"