Documentation
Running it, on our servers or yours.
The same compose file either way. Nothing here is a hosted-only feature, because a compliance product a company cannot run itself is one it cannot audit either.
Streaming to a SIEM
Audit records are also security telemetry, and the team that correlates them already has a tool. Each stored batch is mirrored into it — mirroring, not moving: the event store stays the record.
| Splunk | HTTP Event Collector. Each event in its own envelope, with epoch seconds and your index. |
| Microsoft Sentinel | The Log Analytics data collector endpoint, into the custom table you name. |
| Elasticsearch / OpenSearch | The bulk API, with create and the event id as the document id — so a replayed batch is rejected as a duplicate rather than silently overwriting. |
| Webhook | Any HTTPS endpoint, receiving newline-delimited JSON. |
One field shape across all four. Names match the export and the query API rather than each vendor’s house style, so a customer correlating against their firewall logs writes the field name once, and moving from Splunk to Elastic does not invalidate their saved searches.
# Generate the key once, then give it to both the dashboard and the processor.
dotnet run --project tools/Reldavi.Cli -- siem-key
Siem__Enabled=true
Siem__CredentialKey=<32 random bytes, base64>
Collector tokens are encrypted at rest with a key that lives only in configuration. The dashboard and the processor must hold the same one, and losing it loses the stored tokens rather than any audit data — they have to be re-entered.
Self-hosting
One compose file brings up PostgreSQL, ClickHouse, the message broker, both APIs and the dashboard. On first start it creates your tenant, your owner account and an ingest key, and prints the key once.
cd deploy
cp .env.example .env # change every secret in it
docker compose up -d
# The ingest key is printed once, into the ingestion service's log.
docker compose logs ingestion-api | grep "Ingest API key"
Backup and restore
Self-hosting means the copies are yours to keep. There is one thing about this platform that makes it different from backing up an ordinary application, and it is worth knowing before you need it rather than after.
| Both databases, one backup | build/backup.sh takes them together and writes a manifest with the counts it saw, so a restore has something to compare against. |
| And the secrets beside them | Siem__CredentialKey decrypts every stored SIEM collector token. Lose it and every tenant re-enters theirs by hand. It is in .env, not in either database. |
| Restore the control plane first | Either order works while both halves succeed. This one is chosen so that a failure in the second half leaves you knowing the proof survived. |
| Then prove it | The restore is not finished when the data is back. It is finished when reldavi verify says every sealed day still hashes to its checkpoint — and nothing else will tell you. |
The full procedure, including the drill to run before you rely on any of it, is in docs/runbook-backup-restore.md in the repository. Evidence packs are deliberately not backed up: a pack is an output and can be written again from the two databases, and one already handed to an auditor lives wherever the auditor keeps it.
Observability
The SDK exposes OpenTelemetry metrics. Register the meter and alert on the first row.
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMeter(ReldaviDiagnostics.MeterName));
| Metric | |
|---|---|
| reldavi.events.dropped | Events lost because the buffer was full. Alert on this. Non-zero means you are losing audit coverage. |
| reldavi.capture.duration | Time spent inside the interceptor, on your thread. This is the "no added latency" promise, measured. |
| reldavi.events.delivered | Events acknowledged by the ingestion endpoint. |
| reldavi.batches.failed | Batches abandoned after exhausting retries. |
Rate limits and backpressure
The ingestion endpoint is rate-limited per tenant: a token bucket, 50 requests per second sustained with a burst of 200, and each request may carry up to ten thousand events. The limit bounds request overhead rather than how much you may audit — at the maximum batch size it is half a million events a second, per tenant.
| Partitioned by tenant, not by IP | Every request from your fleet arrives from a handful of NAT addresses, so an IP partition would either throttle you as one client or fail to isolate you from anybody else. |
| A token bucket, not a fixed window | Audit traffic is bursty by nature — a nightly job writes ten thousand rows in a second. A fixed window would reject that burst while your average rate sat far below the limit. |
| A refusal carries `Retry-After` | Which is what turns a limit into backpressure rather than an outage. Without it a well-behaved client retries immediately and makes the overload worse. Every SDK here honours it. |
| Nothing is lost to a 429 | The client keeps the batch and retries it. What loses events is a buffer that fills faster than the endpoint drains for long enough — and that is counted and alertable rather than silent. |
Self-hosting? These are configuration, under RateLimiting. The defaults are chosen for a shared deployment where one tenant must not be able to starve the others; on your own hardware with one tenant, raise them.
English and Turkish
Not a translated marketing site with an English product behind it. The dashboard, the exports, the evidence pack and the verification instructions inside it are all written in both, and each person picks their own — the same tenant can have an English-reading engineer and a Turkish-reading compliance officer looking at the same trail.
| Search folds case across the whole alphabet | Searching öztürk finds Mehmet Öztürk. ASCII-only case folding — the default in a surprising number of systems — leaves Ö and ö as different letters, so a Turkish name is findable only by guessing its capitalisation. It fails in the way nobody reports, too: it does not error, it just returns fewer rows than it should. A walkthrough searches the lowercase form of a name taken from the seeded data and expects the row back. |
| The evidence pack is bilingual | HOW-TO-VERIFY.txt carries both, so the pack can be handed to an auditor without anybody translating a verification procedure by hand. |
| Dates and numbers follow the reader | Not the server. A timestamp an auditor reads in the wrong order is a timestamp that starts an argument. |
What to read next
The schema, the SDK sources and the full architecture notes are open. If something is unclear, that is a bug in our documentation. Get in touch.