Coverage for src / lexigram / contracts / audit / types.py: 2%
65 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Canonical audit-trail value types for security and compliance logging."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
7from enum import StrEnum, auto
8from typing import Any
10__all__ = [
11 "AuditEntry",
12 "AuditEventSeverity",
13 "AuditMismatch",
14 "AuditMismatchReason",
15 "AuditQuery",
16 "RetentionDecision",
17 "RetentionPolicy",
18]
21class AuditEventSeverity(StrEnum):
22 """Severity level for audit log entries."""
24 LOW = auto() # Informational — routine operations
25 MEDIUM = auto() # Notable — privilege changes, config updates
26 HIGH = auto() # Security-sensitive — auth failures, data access
27 CRITICAL = auto() # Compliance-critical — data deletion, impersonation
30class AuditMismatchReason(StrEnum):
31 """Why an audit entry failed checksum verification."""
33 CHECKSUM_MISMATCH = auto() # Stored checksum does not match recomputed checksum
34 NO_CHECKSUM_PRESENT = auto() # Entry has no stored checksum (pre-checksum row)
37@dataclass(frozen=True)
38class AuditEntry:
39 """Immutable record of a security-relevant or compliance-relevant operation.
41 Canonical audit type used by all packages. Supersedes the former
42 ``AuditEvent`` (simpler, no old/new values) and the ``AuditEntry``
43 from ``contracts/observability/audit``.
45 Attributes:
46 action: Dot-notation action identifier (e.g. ``"user.update"``).
47 actor_id: ID of the user or service that performed the action.
48 resource_type: Kind of affected resource (e.g. ``"User"``).
49 resource_id: ID of the affected resource.
50 outcome: ``"success"`` or ``"failure"``.
51 severity: Severity level (defaults to MEDIUM).
52 occurred_at: UTC datetime of the event (defaults to now).
53 metadata: Arbitrary additional context.
54 old_values: Field values before the action (optional).
55 new_values: Field values after the action (optional).
56 source: Originating subsystem (e.g. ``"sql"``, ``"admin"``, ``"ai"``).
57 tenant_id: Multi-tenant scoping (optional).
58 correlation_id: Request correlation ID for tracing (optional).
59 causation_id: Causing event ID for event-driven traces (optional).
60 command_payload_hash: SHA-256 hash of the command payload (optional).
61 payload_size_bytes: Size of the payload in bytes (optional).
62 checksum: HMAC-SHA256 checksum of the persisted row, populated
63 by stores on read-back (None for pre-checksum entries).
64 """
66 action: str
67 actor_id: str
68 resource_type: str = ""
69 resource_id: str = ""
70 outcome: str = "success"
71 severity: AuditEventSeverity = AuditEventSeverity.MEDIUM
72 occurred_at: datetime = field(default_factory=lambda: datetime.now(UTC))
73 metadata: dict[str, Any] = field(default_factory=dict)
74 old_values: dict[str, Any] | None = None
75 new_values: dict[str, Any] | None = None
76 source: str = ""
77 tenant_id: str | None = None
78 correlation_id: str | None = None
79 causation_id: str | None = None
80 command_payload_hash: bytes | None = None
81 payload_size_bytes: int | None = None
82 checksum: str | None = None
85@dataclass(frozen=True)
86class AuditQuery:
87 """Composable filter criteria for querying the audit trail.
89 All fields are optional; omitted fields are not applied as filters.
91 Attributes:
92 actor_id: Filter by actor identifier.
93 action: Filter by action verb (exact match).
94 resource_type: Filter by resource type.
95 resource_id: Filter by resource identifier.
96 source: Filter by originating subsystem.
97 severity: Filter by severity level.
98 outcome: Filter by outcome.
99 tenant_id: Filter by tenant.
100 correlation_id: Filter by request correlation ID.
101 since: Only entries at or after this UTC datetime.
102 until: Only entries at or before this UTC datetime.
103 limit: Maximum number of entries (default 100).
104 offset: Entries to skip for pagination.
105 """
107 actor_id: str | None = None
108 action: str | None = None
109 resource_type: str | None = None
110 resource_id: str | None = None
111 source: str | None = None
112 severity: AuditEventSeverity | None = None
113 outcome: str | None = None
114 tenant_id: str | None = None
115 correlation_id: str | None = None
116 since: datetime | None = None
117 until: datetime | None = None
118 limit: int = 100
119 offset: int = 0
122@dataclass(frozen=True)
123class AuditMismatch:
124 """Record of a checksum verification failure.
126 Attributes:
127 entry_id: Identifier of the audit entry with a bad checksum.
128 expected_checksum: Checksum stored in the database.
129 actual_checksum: Checksum recomputed from current data.
130 reason: Whether the entry is tampered or simply has no stored checksum.
131 """
133 entry_id: str
134 expected_checksum: str
135 actual_checksum: str
136 reason: AuditMismatchReason = AuditMismatchReason.CHECKSUM_MISMATCH
139class RetentionDecision(StrEnum):
140 """Outcome of a retention policy evaluation."""
142 RETAIN = auto() # Keep indefinitely
143 RETAIN_UNTIL = auto() # Keep until expiry
144 ARCHIVE = auto() # Move to cold storage
145 PURGE = auto() # Eligible for deletion
148@dataclass(frozen=True)
149class RetentionPolicy:
150 """Configuration for audit retention behavior.
152 Attributes:
153 name: Identifier for this policy.
154 default_retention_days: Default days to retain entries (0 = indefinite).
155 severity_overrides: Per-severity retention days (key = severity value).
156 source_overrides: Per-source retention days (key = source string).
157 """
159 name: str
160 default_retention_days: int = 365
161 severity_overrides: dict[str, int] = field(default_factory=dict)
162 source_overrides: dict[str, int] = field(default_factory=dict)