Implements a write-behind queue with a background daemon thread in
AuditService so that record() returns immediately without blocking the
calling domain operation on a synchronous SQLite INSERT + COMMIT.
Changes:
- AuditService: add _writer_loop(), _write_payload(), flush() methods
and a write-behind queue (queue.Queue) with a background thread.
record() enqueues the payload and returns a placeholder entry with
id=-1 in async mode; the background thread persists entries in order.
close() calls flush() to drain the queue before closing the session,
ensuring no data loss under normal operation.
- Async mode is only active when the service owns its session (no
injected session), because SQLite connections are thread-local.
Tests that inject a session automatically fall back to sync mode.
- Settings: add audit_async (default True) and audit_queue_maxsize
(default 10000) fields with env-var aliases.
- Existing tests updated to use audit_async=False (sync mode) via
injected sessions — behaviour is unchanged.
- New BDD feature: features/async_audit_recording.feature with 20
scenarios covering non-blocking record(), flush(), close(), context
manager, ordering guarantees, sync fallback, and settings defaults.
Ordering guarantees (best-effort):
Entries are written in enqueue order by the single background thread.
created_at timestamps are set at enqueue time so they reflect the
logical event time regardless of write latency.
Backward compatibility:
The public API of AuditService is unchanged. Callers that need the
real database id immediately should use audit_async=False or call
flush() after record(). The placeholder id=-1 signals pending
persistence.
ISSUES CLOSED: #718