Feature: Async audit recording (issue #718) As a system operator I want audit recording to be non-blocking So that domain operations are not delayed by SQLite writes # ── Non-blocking record() ───────────────────────────────────── Scenario: record() returns immediately without blocking in async mode Given an async audit service When I record a "plan_applied" event in async mode Then the record call should return immediately And the returned entry should have event_type "plan_applied" Scenario: Async record returns placeholder id of -1 Given an async audit service When I record a "plan_applied" event in async mode Then the returned entry id should be -1 Scenario: Entries are persisted after flush Given an async audit service When I record a "plan_applied" event in async mode And I flush the async audit service Then the audit log should contain 1 persisted entry Scenario: Multiple entries are all persisted after flush Given an async audit service When I record 5 "plan_applied" events in async mode And I flush the async audit service Then the audit log should contain 5 persisted entries Scenario: Flush waits for all enqueued entries to be written Given an async audit service When I record 10 "config_changed" events in async mode And I flush the async audit service Then the audit log should contain 10 persisted entries # ── No data loss ────────────────────────────────────────────── Scenario: Close flushes all pending entries before shutting down Given an async audit service When I record 3 "session_created" events in async mode And I close the async audit service Then the audit log should contain 3 persisted entries after close Scenario: Context manager flushes on exit Given an async audit service used as context manager When I record a "plan_cancelled" event inside the context manager Then the audit log should contain 1 persisted entry after context exit # ── Synchronous fallback ────────────────────────────────────── Scenario: Synchronous mode returns real id immediately Given a synchronous audit service When I record a "plan_applied" event synchronously Then the returned entry id should be a positive integer Scenario: Synchronous mode persists immediately without flush Given a synchronous audit service When I record a "plan_applied" event synchronously Then the audit log should contain 1 persisted entry immediately # ── Settings ────────────────────────────────────────────────── Scenario: Default audit_async setting is True Given default settings Then audit_async should be True Scenario: audit_async can be disabled via settings Given settings with audit_async False Then audit_async should be False Scenario: audit_queue_maxsize default is 10000 Given default settings Then audit_queue_maxsize should be 10000 # ── Background thread ───────────────────────────────────────── Scenario: Background writer thread is started in async mode Given an async audit service Then the background writer thread should be alive Scenario: Background writer thread stops after flush Given an async audit service When I flush the async audit service Then the background writer thread should be stopped # ── Ordering guarantees ─────────────────────────────────────── Scenario: Entries are written in enqueue order Given an async audit service When I record events with plan_ids "first" "second" "third" in async mode And I flush the async audit service Then the entries should be persisted in enqueue order # ── Error resilience ────────────────────────────────────────── Scenario: Invalid event_type raises ValueError immediately in async mode Given an async audit service When I record an event with invalid type "not_a_real_event" in async mode Then a ValueError should be raised immediately Scenario: flush() is idempotent Given an async audit service When I flush the async audit service And I flush the async audit service again Then no async audit error should be raised Scenario: close() is idempotent Given an async audit service When I close the async audit service And I close the async audit service again Then no async audit error should be raised