Files
cleveragents-core/features/async_audit_recording.feature
freemo f0a40afecc
CI / build (push) Successful in 22s
CI / lint (push) Failing after 24s
CI / helm (push) Successful in 24s
CI / quality (push) Successful in 3m41s
CI / typecheck (push) Successful in 3m55s
CI / coverage (push) Has been skipped
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m4s
CI / unit_tests (push) Failing after 5m43s
CI / docker (push) Has been skipped
CI / benchmark-publish (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / status-check (push) Has been cancelled
refactor(audit): implement async audit recording to unblock event pipeline (#1279)
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 with a background thread
- record() enqueues the payload and returns a placeholder entry with id=-1 in async mode
- close() calls flush() to drain the queue before closing the session
- Settings: add audit_async (default True) and audit_queue_maxsize (default 10000) fields
- 20 BDD scenarios covering non-blocking record(), flush/close lifecycle, ordering, sync fallback, and settings

Closes #718

Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
2026-04-02 17:00:53 +00:00

113 lines
4.9 KiB
Gherkin

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