The acceptance standard is not “a row exists.” Evidence must be formed at the correct boundary, remain tenant-safe and nonsecret, fail visibly, preserve category semantics through query and retention, and let an operator identify the affected time, instance, and record range.
1. Current automated evidence
Audit tests are distributed across four layers:
| Layer | Representative tests | Main contract |
|---|---|---|
| Unit | AuditBatchIntegrityTests, ChannelAuditQueueTests, AuditBatchWriterTests | stable IDs, conflicting evidence, backpressure, timed batches, retry, shutdown |
| Unit | AuditEntryNormalizerTests, AuditEnvelopeMapperTests, ExternalRequestLoggingHandlerTests | category mapping, redaction, URL boundary, public projection |
| Unit | EfCoreAuditSaveChangesInterceptorTests, SqlSugarEntityChangeAuditorTests, EntityChangeAuditOutboxTests | commit awareness, rollback, sensitive fields, transactional Outbox |
| Application | AuditRetentionPolicyServiceTests, AuditAlertServiceTests, SiemForwardServiceTests | policy versioning, rule evaluation, SIEM watermark/DLQ/backoff |
| Integration | DapperAuditQueryIntegrationTests, AuditRetentionEndpointTests, CapOutboxIntegrationTests | SQL split-table query, HTTP permission/error, real Outbox |
| Architecture | AuditingPersistenceArchitectureTests, MongoAuditArchitectureTests, AuditSinkContractCohesionTests | adapter dependencies, category coverage, contract cohesion |
Structure tests prove that types, tokens, or registrations exist; they do not prove runtime semantics. Cross-provider ordering, real database rollback, process crashes, and high-volume pruning need integration or process fixtures.
2. Suggested commands
Run from the BitzOrcasVNext root with the repository-pinned SDK:
# General audit, persistence, and outbound-HTTP unit tests.dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj --filter "FullyQualifiedName~Auditing|FullyQualifiedName~Audit|FullyQualifiedName~ExternalRequestLoggingHandler"
# Alert, export, retention-policy, and SIEM application tests.dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj --filter "FullyQualifiedName~Auditing"
# SQL query, retention HTTP surface, and CAP Outbox integration evidence.dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj --filter "FullyQualifiedName~Audit|FullyQualifiedName~CapOutbox"
# Dependency direction and adapter contracts.dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj --filter "FullyQualifiedName~Audit"Before relying on an OR filter, use dotnet test --list-tests to confirm actual matches. A command that selects zero tests is not a green gate.
3. Producer matrix
Every source needs success, business failure, system exception, cancellation, and context coverage:
| Source | Required branches | Key assertions |
|---|---|---|
| HTTP | 2xx, 4xx, 401/403/429, 500, disconnect | effective tenant/actor; original response and exception preserved |
| Mediator | Success, Result Failure, throw, [AuditIgnore] | stable error, generated policy, complementary exception evidence |
| EF Core | no transaction, explicit transaction, CAP transaction, rollback, unknown commit | committed changes only, string redaction, version restoration |
| SqlSugar | INSERT/UPDATE/DELETE, unparsed SQL, rollback | table/action only, no SQL/parameters, infrastructure exclusion |
| HttpClient | 2xx/4xx/5xx, DNS, timeout, cancellation | null bodies, no query/user info, status 0 for transport failure |
| CAP | success, exception, duplicate execution key, sink failure | dedicated category, exception type, duration, unchanged consumer outcome |
| Job | Success, Result Failure, throw, scheduler cancellation | job name, system actor, platform tenant, preserved exception |
| Exception | Warning, Error, Fatal, alert failure | redaction, bounds, alert does not replace evidence |
4. Alert-rule operations
The alert API includes:
GET/PUT /api/audit/alertsfor list and save;DELETE /api/audit/alerts/{ruleId}with optimistic version soft deletion;POST /api/audit/alerts/{ruleId}/toggle;POST /api/audit/alerts/{ruleId}/dry-run, count only;GET /api/audit/alerts/historyfor trigger and notification outcomes.
Rules accept only controlled Category, Success/Failure result, threshold 1–10,000, window 1–1,440 minutes, cooldown 0–1,440 minutes, and semicolon-separated recipient identifiers. Notification interprets recipients; rules hold no URL or credential.
Every minute AuditAlertEvaluationJobExecutor enumerates tenants, counts events in the window, publishes audit.alert.triggered after threshold and cooldown checks, and stores notification success/failure history.
Alert save also uses auditing.audit.view. As with retention policy, restrict this permission to audit administrators until management actions are split.
5. SIEM management and delivery
Management API
| Route | Operation |
|---|---|
GET/PUT /api/operations/siem/config | read/save tenant configuration |
GET /api/operations/siem/metrics | Sent/Failed/Retried/Dropped, watermark, last error |
GET /api/operations/siem/deliveries | Pending/Retrying/Dropped DLQ list |
POST .../deliveries/{recordId}/replay | requeue one record |
POST .../deliveries/{recordId}/drop | mark one record terminally dropped |
POST /api/operations/siem/test | send without advancing watermark or creating DLQ |
Read uses operations.siem.view; configuration, replay, drop, and test use Update. Management changes attempt an Activity audit, but that best-effort record does not block a saved configuration.
Controlled profiles
| ProfileId | Transport | Default port | Format |
|---|---|---|---|
syslog-rfc5424-udp | UDP | 514 | CEF |
syslog-rfc5424-tcp | TCP | 514 | CEF |
syslog-rfc5424-tls | TLS | 6514 | CEF |
An optional TLS CA is resolved by CredentialName through ISecretStore; a missing secret skips the round rather than downgrading to cleartext. Configuration bounds BatchSize to 1–1,000, MaxRetryAttempts to 0–100, validates the port, and uses a version to prevent lost updates.
Runtime semantics
The SIEM job enumerates tenants every 30 seconds. Each round first retries at most 100 due DLQ entries, then queries configured categories, formats CEF, and sends. A failed new event enters DLQ with an initial 30-second delay; later failures use exponential backoff capped at 256 seconds and eventually become Dropped. Test events affect neither watermark nor DLQ.
Category configuration currently validates only nonblank text, not membership in AuditCategory. If every configured value is unknown, forwarding returns Success with no sends. The UI should use catalog values only, and the API should reject unknown values.
SiemForwardJobExecutor also accumulates failed tenants but returns Success. Operators must inspect tenant metrics, LastError, watermark stagnation, and DLQ rather than relying on Quartz state alone.
6. Existing signals and gaps
Available signals include:
ChannelAuditQueue.RejectedCount, alerted on the first and every 1,000th rejection;- writer Debug batch latency, Error retry attempt, and first/every-tenth failure alert;
- persisted SIEM Sent/Failed/Retried/Dropped, watermark, LastError, and UpdatedAt;
- alert trigger history and Notification state;
- retention Tenant/Scope/Days/Deleted structured logs plus Activity/Exception evidence.
The general pipeline has no dedicated System.Diagnostics.Metrics meter and exposes no supported Queue Depth, Oldest Age, Enqueue Rate, or Persist p95. Add low-cardinality instruments before documenting concrete metric names. TenantId, UserId, CorrelationId, Path, and free-form Module do not belong in metric labels.
7. On-call thresholds
| Signal | Severity | First action |
|---|---|---|
| RejectedCount increases | Critical | inspect Store, pool, writer retry; bound synchronous failures |
| one batch retries continuously | Critical | shed optional load, protect DB connections, verify stable-ID retry |
| shutdown grace expires | Critical | mark possible-loss instance/time window and preserve node events |
| any tenant/scope retention failure | Critical | do not blindly prune manually; verify holds and remaining range |
| SIEM watermark unchanged across two rounds | Critical | inspect query direction, DLQ, credentials, endpoint; do not drop |
| DLQ Dropped increases | Security incident | retain CEF/error/ticket and decide replay versus accepted gap |
| alert dry run differs from actual | Critical | suspend automated response and inspect ResultCondition |
| query p95/table scans rise | Warning | inspect time predicate, split count, 10,000 window, indexes |
8. Diagnosis flow
Before remediation, preserve instance, deployment version, time range, Category, Tenant, and last known AuditId. Do not immediately run broad pruning, DLQ drop, or duplicate business commands; those actions can obscure the original failure boundary.
9. Pre-release fault drills
- fill a Capacity-1 Channel and verify async wait, sync rejection, sampled alert;
- fail the Store repeatedly and recover it, proving same-batch retry, no conflicting duplicate, and released upstream backpressure;
- create an unknown SqlSugar commit and Mongo cross-collection partial completion, proving stable-ID repair;
- run SIGTERM, expired grace period, and SIGKILL separately and document provable/unprovable windows;
- verify pre-enqueue redaction with JSON secret, Bearer, URI password, card number, and oversized payload;
- compare seven categories, equal timestamps, second page, and cursor across SqlSugar/Mongo/Dapper;
- prune two tenants by every scope, preserving permanent classes and the other tenant;
- verify alert ResultCondition with separate Success and Failure fixtures;
- execute SIEM twice with a new event between runs, then exercise missing TLS secret, DLQ retry, replay, and drop;
- fail Notification, Secret Store, tenant directory, and policy store and ensure job state reports failure honestly.
10. GA gates
At shift handoff, preserve aggregate SIEM metrics first and export actionable deliveries separately. Record both capture times in the incident:
# Capture watermark, failure, retry, and drop counters first.curl -fsS -H "Authorization: Bearer <TOKEN>" \ https://<HOST>/api/operations/siem/metrics > siem-metrics.json# Preserve delivery rows that need an explicit replay/drop decision.curl -fsS -H "Authorization: Bearer <TOKEN>" \ https://<HOST>/api/operations/siem/deliveries > siem-deliveries.json- no silent drop in the general queue; retry, idempotency, shutdown, and forced-termination boundaries are in the SLO;
- high-value entity changes use the CAP transactional Outbox and non-CAP fallback has an explicit deployment restriction;
- seven-category fidelity across SqlSugar/Mongo/Dapper, with the SqlSugar TraceId gap closed;
- regression coverage for JSON/form/URI/token/card and length bounds; outbound bodies remain uncollected;
- retention runs by Tenant+Scope, host defaults are floor-gated, and preview labels the Request boundary;
- alert ResultCondition affects query and tenant failure propagates to job result;
- SIEM two-round watermark is green, unknown Category is rejected, and failed tenants fail the job;
AutoCreateTablesandAuditShardingScheduleare either wired or removed from public configuration;- required SLO signals such as Queue Depth/Oldest Age have supported meters rather than log estimates;
- Production prune, SIEM drop, and cross-tenant investigation have separate permission, approval, purpose, and non-prunable evidence.