perf(audit): bounded async audit writer with batched inserts, one-time bootstrap, retention sweep
This commit is contained in:
@@ -228,6 +228,31 @@ Storage recommendation:
|
||||
administrators.
|
||||
- Require TLS when the gateway is reachable off-machine.
|
||||
|
||||
## Audit Pipeline
|
||||
|
||||
Decision: audit is asynchronous, bounded, and swept.
|
||||
|
||||
The canonical `IAuditWriter` contract has always been best-effort — a failed audit write is
|
||||
logged and swallowed so it cannot abort the action that produced it. The registered writer is
|
||||
`ChannelAuditWriter`, which makes the cost of that promise explicit: a producer enqueues onto a
|
||||
4096-event bounded channel and returns, and `AuditDrainService` commits up to 64 buffered events
|
||||
per transaction. This exists because constraint denials are emitted per denied tag inside bulk
|
||||
RPC loops: a partially denied 1,000-tag request previously awaited 1,000 sequential SQLite
|
||||
inserts — each re-running `CREATE TABLE IF NOT EXISTS` — against the same database file every
|
||||
authenticated call reads. The schema bootstrap now runs once, from the drain's `StartAsync`.
|
||||
|
||||
When the channel is full the newest event is dropped and counted rather than blocking the
|
||||
producer: a stalled audit database must cost audit completeness, not gateway availability. Drops
|
||||
are logged once and reported in aggregate on each sweep. Shutdown drains what is buffered under a
|
||||
2-second cap. Where no hosted service runs — the `apikey` admin CLI — the writer falls back to the
|
||||
synchronous path, so audit is never buffered into a channel nobody drains.
|
||||
|
||||
`MxGateway:Security:AuditRetentionDays` (default 90, minimum 1) bounds the table: the drain sweeps
|
||||
at startup and hourly, deleting older rows. Retention cannot be configured off. The sweep compares
|
||||
through SQLite's `datetime()` rather than on the stored ISO-8601 text, because timestamps written
|
||||
from a non-UTC offset do not sort lexicographically against a UTC cutoff; a row whose timestamp
|
||||
cannot be parsed yields NULL and is kept rather than deleted.
|
||||
|
||||
## Authorization
|
||||
|
||||
Decision: start with scope checks by command category.
|
||||
|
||||
Reference in New Issue
Block a user