Files
git.stella-ops.org/docs/features/checked/policy/exception-application-audit-trail.md
2026-02-13 02:04:55 +02:00

37 lines
3.4 KiB
Markdown

# Exception Application Audit Trail (policy.exception_applications)
## Module
Policy
## Status
IMPLEMENTED
## Description
Records every instance of an exception being applied to a finding in a dedicated `policy.exception_applications` table, capturing exception ID, finding context, original and applied status, purl, vulnerability ID, and evaluation run ID. Exposed via ledger export for compliance.
## Implementation Details
- **ExceptionApplication Model**: `src/Policy/__Libraries/StellaOps.Policy.Exceptions/Models/ExceptionApplication.cs`
- Sealed record with fields: Id (Guid), TenantId, ExceptionId, FindingId, VulnerabilityId, OriginalStatus, AppliedStatus, EffectName, EffectType, EvaluationRunId, PolicyBundleDigest, AppliedAt, Metadata
- `Create()` static factory method enforces non-null ExceptionId/FindingId, accepts deterministic applicationId and appliedAt timestamps
- Metadata stored as `ImmutableDictionary<string, string>` for extensibility
- **IExceptionApplicationRepository**: `src/Policy/__Libraries/StellaOps.Policy.Exceptions/Repositories/IExceptionApplicationRepository.cs`
- `RecordAsync(application)` -- persists a single application record
- `RecordBatchAsync(applications)` -- bulk persist for batch evaluation
- Query by ExceptionId, FindingId, VulnerabilityId, EvaluationRunId, TimeRange
- `GetStatisticsAsync(tenantId, filter?)` returns `ExceptionApplicationStatistics` (TotalApplications, UniqueExceptions, UniqueFindings, UniqueVulnerabilities, ByEffectType counts, ByAppliedStatus counts, EarliestApplication, LatestApplication)
- `CountAsync(tenantId, filter?)` for total count with optional filter
- `ExceptionApplicationFilter` record supports paging (Limit/Offset), date range (FromDate/ToDate), and field filters
- **PostgresExceptionApplicationRepository**: `src/Policy/__Libraries/StellaOps.Policy.Exceptions/Repositories/PostgresExceptionApplicationRepository.cs` -- Postgres persistence for the `policy.exception_applications` table
- **ExceptionEvaluator**: `src/Policy/__Libraries/StellaOps.Policy.Exceptions/Services/ExceptionEvaluator.cs` -- creates ExceptionApplication records when exceptions match findings during policy evaluation
## E2E Test Plan
- [ ] Apply exception to finding; query `GetByExceptionIdAsync(tenantId, exceptionId)`; verify record includes correct ExceptionId, FindingId, OriginalStatus, AppliedStatus, EffectName, EffectType
- [ ] Apply exception with VulnerabilityId; query `GetByVulnerabilityIdAsync(tenantId, vulnId)`; verify record returned with correct VulnerabilityId
- [ ] Apply exception during batch evaluation; verify EvaluationRunId is populated; query `GetByEvaluationRunIdAsync(tenantId, runId)` and verify all applications for that run
- [ ] Apply exception; verify AppliedAt timestamp matches evaluation time (deterministic)
- [ ] Apply exception with PolicyBundleDigest; verify digest is recorded in the application record
- [ ] Call `RecordBatchAsync` with 5 applications; verify all 5 are persisted
- [ ] Call `GetByTimeRangeAsync(tenantId, from, to)` with a range encompassing 3 applications; verify exactly 3 returned
- [ ] Call `GetStatisticsAsync(tenantId)` after 10 applications across 3 exceptions and 5 findings; verify TotalApplications=10, UniqueExceptions=3, UniqueFindings=5, ByEffectType counts sum to 10
- [ ] Call `CountAsync(tenantId, filter)` with EffectType="suppress" filter; verify count matches expected