37 lines
3.4 KiB
Markdown
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
|