feat(application): audit abstractions (UDT-010 B2)

Introduces the contract layer for audit logging per design #D-8:

SIGCM2.Application/Audit/:
- IAuditContext — request-scoped accessor for ActorUserId/ActorRoleId/
  Ip/UserAgent/CorrelationId. Populated by CorrelationIdMiddleware +
  AuditActorMiddleware (B4).
- IAuditLogger.LogAsync(action, targetType, targetId, metadata?, ct) —
  domain-level audit emitter. Enlists in ambient TransactionScope
  (fail-closed per #REQ-AUD-4).
- ISecurityEventLogger.LogAsync(action, result, actorUserId?, attemptedUsername?,
  sessionId?, failureReason?, metadata?, ct) — security-events emitter
  separate from IAuditLogger (different retention, no transaction scope,
  captures login/logout/refresh/permission.denied).
- AuditOptions — bindable POCO with SanitizedKeys[] defaults (used by
  JsonSanitizer in B3).
- AuditEventDto — read projection for GET /api/v1/audit/events (B10).
- AuditEventFilter — query filter record with default Limit=50.

SIGCM2.Domain/Exceptions/:
- AuditContextMissingException : DomainException — fail-closed sentinel
  thrown when IAuditLogger is called without ActorUserId in a user-scoped
  command (#REQ-AUD-4).

Tests (Strict TDD — shape contract, RED → GREEN):
- tests/SIGCM2.Application.Tests/Audit/AuditAbstractionsTests.cs: 11 tests
  covering nullability, signatures, default options, record equality.

Suite: 336/336 Application.Tests (prev 325 + 11 new). 130/130 Api.Tests.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-3/4/5, design#D-8, tasks#B2}
This commit is contained in:
2026-04-16 13:23:11 -03:00
parent c95bc7fe01
commit 68f96b90c7
8 changed files with 290 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
namespace SIGCM2.Application.Audit;
/// Read projection of dbo.AuditEvent for GET /api/v1/audit/events.
/// ActorUsername is resolved by join against dbo.Usuario at query time (denormalized in the DTO).
public sealed record AuditEventDto(
long Id,
DateTime OccurredAt,
int? ActorUserId,
string? ActorUsername,
string Action,
string TargetType,
string TargetId,
Guid? CorrelationId,
string? IpAddress,
string? Metadata);