Files
git.stella-ops.org/docs/modules/audit/AUDIT_EMISSION_GUIDE.md
master 189171c594 Sidebar 5-group restructure + demo data badges + audit emission infrastructure
Sprint 4 — Sidebar restructure (S4-T01+T02):
  5 groups: Release Control, Security, Operations, Audit & Evidence, Setup & Admin
  Groups 4+5 collapsed by default for new users
  Operations extracted from Release Control into own group
  Audit extracted from Security into own group
  groupOrder and resolveMenuGroupLabel updated
  Approvals badge moved to section-level

Sprint 2 — Demo data badges (S2-T04+T05):
  Backend: isDemo=true on all compatibility/seed responses in
    PackAdapterEndpoints, QuotaCompatibilityEndpoints, VulnerabilitiesController
  Frontend: "(Demo)" badges on Usage & Limits page quotas
  Frontend: "(Demo)" badges on triage artifact list when seed data
  New PlatformItemResponse/PlatformListResponse with IsDemo field

Sprint 6 — Audit emission infrastructure (S6-T01+T02):
  New shared library: src/__Libraries/StellaOps.Audit.Emission/
    - AuditActionAttribute: [AuditAction("module", "action")] endpoint tag
    - AuditActionFilter: IEndpointFilter that auto-emits UnifiedAuditEvent
    - HttpAuditEventEmitter: POSTs to Timeline /api/v1/audit/ingest
    - Single-line DI: services.AddAuditEmission(configuration)
  Timeline service: POST /api/v1/audit/ingest ingestion endpoint
    - IngestAuditEventStore: 10k-event ring buffer
    - CompositeUnifiedAuditEventProvider: merges HTTP-polled + ingested
  Documentation: docs/modules/audit/AUDIT_EMISSION_GUIDE.md

Angular build: 0 errors. .NET builds: 0 errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 14:48:18 +02:00

6.1 KiB

Audit Event Emission Guide

This guide explains how to add automatic audit event emission to any StellaOps service using the shared StellaOps.Audit.Emission library.

Overview

The audit emission infrastructure provides:

  1. AuditActionAttribute -- marks an endpoint for automatic audit event emission
  2. AuditActionFilter -- ASP.NET Core endpoint filter that creates and sends UnifiedAuditEvent payloads
  3. HttpAuditEventEmitter -- posts events to the Timeline service's POST /api/v1/audit/ingest endpoint
  4. AddAuditEmission() -- single-line DI registration

Events flow: Service endpoint -> AuditActionFilter -> HttpAuditEventEmitter -> Timeline /api/v1/audit/ingest -> IngestAuditEventStore -> merged into unified audit query results.

Step 1: Add project reference

In your service's .csproj, add a reference to the shared library:

<ProjectReference Include="..\..\__Libraries\StellaOps.Audit.Emission\StellaOps.Audit.Emission.csproj" />

Adjust the relative path as needed for your service's location in the repo.

Step 2: Register in DI (Program.cs)

Add a single line to your service's Program.cs:

using StellaOps.Audit.Emission;

// After other service registrations:
builder.Services.AddAuditEmission(builder.Configuration);

This registers:

  • AuditActionFilter (scoped endpoint filter)
  • HttpAuditEventEmitter as IAuditEventEmitter (singleton)
  • AuditEmissionOptions bound from configuration

Step 3: Tag endpoints

Add the AuditActionFilter and AuditActionAttribute metadata to any endpoint you want audited:

using StellaOps.Audit.Emission;

app.MapPost("/api/v1/environments", CreateEnvironment)
    .AddEndpointFilter<AuditActionFilter>()
    .WithMetadata(new AuditActionAttribute("concelier", "create"));

app.MapPut("/api/v1/environments/{id}", UpdateEnvironment)
    .AddEndpointFilter<AuditActionFilter>()
    .WithMetadata(new AuditActionAttribute("concelier", "update"));

app.MapDelete("/api/v1/environments/{id}", DeleteEnvironment)
    .AddEndpointFilter<AuditActionFilter>()
    .WithMetadata(new AuditActionAttribute("concelier", "delete"));

Attribute parameters

Parameter Required Description
module Yes Module name from UnifiedAuditCatalog.Modules (e.g., "authority", "policy", "jobengine", "vex", "scanner", "integrations")
action Yes Action name from UnifiedAuditCatalog.Actions (e.g., "create", "update", "delete", "promote", "approve")
ResourceType No Optional resource type override. If omitted, inferred from the URL path segment after the version prefix.

Step 4: Configuration (optional)

The emitter reads configuration from the AuditEmission section or environment variables:

{
  "AuditEmission": {
    "TimelineBaseUrl": "http://timeline.stella-ops.local",
    "Enabled": true,
    "TimeoutSeconds": 3
  }
}

Environment variable overrides:

  • STELLAOPS_TIMELINE_URL -- overrides TimelineBaseUrl
  • AuditEmission__Enabled -- set to false to disable emission
  • AuditEmission__TimeoutSeconds -- HTTP timeout for ingest calls

How the filter works

  1. The endpoint executes normally and returns its result to the caller.
  2. After execution, the filter checks for AuditActionAttribute metadata.
  3. If present, it builds an AuditEventPayload containing:
    • Module and Action from the attribute
    • Actor from HttpContext.User claims (sub, name, email, stellaops:tenant)
    • Resource from route parameters (first matching id, resourceId, etc., or first GUID value)
    • Severity inferred from HTTP response status code (2xx=info, 4xx=warning, 5xx=error)
    • Description auto-generated: "{Action} {module} resource {resourceId}"
    • CorrelationId from X-Correlation-Id header or HttpContext.TraceIdentifier
  4. The event is posted asynchronously (fire-and-forget) to POST /api/v1/audit/ingest.
  5. Failures are logged but never propagated -- audit emission must not affect the endpoint response.

Timeline ingest endpoint

The Timeline service exposes:

POST /api/v1/audit/ingest
  • Auth: Requires timeline:write scope
  • Body: JSON matching the AuditEventPayload schema (camelCase)
  • Response: 202 Accepted with { "eventId": "...", "status": "accepted" }
  • Gateway route: Already covered by the existing /api/v1/audit(.*) route in router-gateway-local.json

Ingested events are stored in an in-memory ring buffer (max 10,000 events) and merged with the HTTP-polled events from other modules (JobEngine, Policy, EvidenceLocker, Notify) in the unified audit query results.

Architecture decisions

  • Fire-and-forget emission: Audit events are sent asynchronously after the endpoint responds. This ensures zero latency impact on the audited endpoint.
  • No compile-time dependency on Timeline: The AuditEventPayload DTOs in the emission library are wire-compatible with UnifiedAuditEvent but live in a separate namespace, avoiding circular dependencies.
  • In-memory ingest store: For the alpha phase, ingested events are stored in memory. A future sprint will add Postgres persistence for the ingest store.
  • Composite event provider: The Timeline service merges HTTP-polled events with ingested events, so all audit data appears in a single unified stream.

File locations

File Path
Shared library src/__Libraries/StellaOps.Audit.Emission/
Attribute src/__Libraries/StellaOps.Audit.Emission/AuditActionAttribute.cs
Filter src/__Libraries/StellaOps.Audit.Emission/AuditActionFilter.cs
Emitter src/__Libraries/StellaOps.Audit.Emission/HttpAuditEventEmitter.cs
DI extension src/__Libraries/StellaOps.Audit.Emission/AuditEmissionServiceExtensions.cs
Ingest endpoint src/Timeline/StellaOps.Timeline.WebService/Endpoints/UnifiedAuditEndpoints.cs
Ingest store src/Timeline/StellaOps.Timeline.WebService/Audit/IngestAuditEventStore.cs
Composite provider src/Timeline/StellaOps.Timeline.WebService/Audit/CompositeUnifiedAuditEventProvider.cs