feat: Implement IsolatedReplayContext for deterministic audit replay
- Added IsolatedReplayContext class to provide an isolated environment for replaying audit bundles without external calls. - Introduced methods for initializing the context, verifying input digests, and extracting inputs for policy evaluation. - Created supporting interfaces and options for context configuration. feat: Create ReplayExecutor for executing policy re-evaluation and verdict comparison - Developed ReplayExecutor class to handle the execution of replay processes, including input verification and verdict comparison. - Implemented detailed drift detection and error handling during replay execution. - Added interfaces for policy evaluation and replay execution options. feat: Add ScanSnapshotFetcher for fetching scan data and snapshots - Introduced ScanSnapshotFetcher class to retrieve necessary scan data and snapshots for audit bundle creation. - Implemented methods to fetch scan metadata, advisory feeds, policy snapshots, and VEX statements. - Created supporting interfaces for scan data, feed snapshots, and policy snapshots.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of, throwError } from 'rxjs';
|
||||
|
||||
import { ExceptionApprovalQueueComponent } from './exception-approval-queue.component';
|
||||
@@ -50,7 +51,17 @@ describe('ExceptionApprovalQueueComponent', () => {
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ExceptionApprovalQueueComponent],
|
||||
providers: [{ provide: EXCEPTION_API, useValue: mockExceptionApi }],
|
||||
providers: [
|
||||
{ provide: EXCEPTION_API, useValue: mockExceptionApi },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: { paramMap: { get: () => null } },
|
||||
params: of({}),
|
||||
queryParams: of({}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ExceptionApprovalQueueComponent);
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
class="status-chip"
|
||||
[style.borderColor]="col.color"
|
||||
[class.active]="filter().status?.includes(col.status)"
|
||||
(click)="updateFilter('status', filter().status?.includes(col.status)
|
||||
? filter().status?.filter(s => s !== col.status)
|
||||
: [...(filter().status || []), col.status])"
|
||||
(click)="toggleStatusFilter(col.status)"
|
||||
>
|
||||
{{ col.label }}
|
||||
<span class="chip-count">{{ statusCounts()[col.status] || 0 }}</span>
|
||||
@@ -70,9 +68,7 @@
|
||||
<button
|
||||
class="filter-chip"
|
||||
[class.active]="filter().type?.includes($any(type))"
|
||||
(click)="updateFilter('type', filter().type?.includes($any(type))
|
||||
? filter().type?.filter(t => t !== type)
|
||||
: [...(filter().type || []), type])"
|
||||
(click)="toggleTypeFilter($any(type))"
|
||||
>
|
||||
{{ type | titlecase }}
|
||||
</button>
|
||||
@@ -88,9 +84,7 @@
|
||||
class="filter-chip"
|
||||
[class]="'sev-' + sev"
|
||||
[class.active]="filter().severity?.includes(sev)"
|
||||
(click)="updateFilter('severity', filter().severity?.includes(sev)
|
||||
? filter().severity?.filter(s => s !== sev)
|
||||
: [...(filter().severity || []), sev])"
|
||||
(click)="toggleSeverityFilter(sev)"
|
||||
>
|
||||
{{ sev | titlecase }}
|
||||
</button>
|
||||
@@ -105,9 +99,7 @@
|
||||
<button
|
||||
class="filter-chip tag"
|
||||
[class.active]="filter().tags?.includes(tag)"
|
||||
(click)="updateFilter('tags', filter().tags?.includes(tag)
|
||||
? filter().tags?.filter(t => t !== tag)
|
||||
: [...(filter().tags || []), tag])"
|
||||
(click)="toggleTagFilter(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
|
||||
@@ -152,6 +152,38 @@ export class ExceptionCenterComponent {
|
||||
this.showFilters.update((v) => !v);
|
||||
}
|
||||
|
||||
toggleStatusFilter(status: ExceptionStatus): void {
|
||||
const current = this.filter().status || [];
|
||||
const newStatuses = current.includes(status)
|
||||
? current.filter((s) => s !== status)
|
||||
: [...current, status];
|
||||
this.updateFilter('status', newStatuses.length > 0 ? newStatuses : undefined);
|
||||
}
|
||||
|
||||
toggleTypeFilter(type: ExceptionType): void {
|
||||
const current = this.filter().type || [];
|
||||
const newTypes = current.includes(type)
|
||||
? current.filter((t) => t !== type)
|
||||
: [...current, type];
|
||||
this.updateFilter('type', newTypes.length > 0 ? newTypes : undefined);
|
||||
}
|
||||
|
||||
toggleSeverityFilter(severity: string): void {
|
||||
const current = this.filter().severity || [];
|
||||
const newSeverities = current.includes(severity)
|
||||
? current.filter((s) => s !== severity)
|
||||
: [...current, severity];
|
||||
this.updateFilter('severity', newSeverities.length > 0 ? newSeverities : undefined);
|
||||
}
|
||||
|
||||
toggleTagFilter(tag: string): void {
|
||||
const current = this.filter().tags || [];
|
||||
const newTags = current.includes(tag)
|
||||
? current.filter((t) => t !== tag)
|
||||
: [...current, tag];
|
||||
this.updateFilter('tags', newTags.length > 0 ? newTags : undefined);
|
||||
}
|
||||
|
||||
updateFilter(key: keyof ExceptionFilter, value: unknown): void {
|
||||
this.filter.update((f) => ({ ...f, [key]: value }));
|
||||
}
|
||||
@@ -193,22 +225,22 @@ export class ExceptionCenterComponent {
|
||||
);
|
||||
}
|
||||
|
||||
getStatusIcon(status: ExceptionStatus): string {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
return '[D]';
|
||||
case 'pending_review':
|
||||
return '[?]';
|
||||
case 'approved':
|
||||
return '[+]';
|
||||
case 'rejected':
|
||||
return '[~]';
|
||||
case 'expired':
|
||||
return '[X]';
|
||||
case 'revoked':
|
||||
return '[!]';
|
||||
default:
|
||||
return '[-]';
|
||||
getStatusIcon(status: ExceptionStatus): string {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
return '[D]';
|
||||
case 'pending_review':
|
||||
return '[?]';
|
||||
case 'approved':
|
||||
return '[+]';
|
||||
case 'rejected':
|
||||
return '[~]';
|
||||
case 'expired':
|
||||
return '[X]';
|
||||
case 'revoked':
|
||||
return '[!]';
|
||||
default:
|
||||
return '[-]';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@if (exception() as exc) {
|
||||
@if (exception()) {
|
||||
<div class="detail-container">
|
||||
<header class="detail-header">
|
||||
<div>
|
||||
<h3 class="detail-title">{{ exc.displayName ?? exc.name }}</h3>
|
||||
<p class="detail-subtitle">{{ exc.exceptionId }}</p>
|
||||
<h3 class="detail-title">{{ exception()!.displayName ?? exception()!.name }}</h3>
|
||||
<p class="detail-subtitle">{{ exception()!.exceptionId }}</p>
|
||||
</div>
|
||||
<button class="btn-link" (click)="closePanel()">Close</button>
|
||||
</header>
|
||||
@@ -16,19 +16,19 @@
|
||||
<div class="detail-grid">
|
||||
<div>
|
||||
<span class="detail-label">Status</span>
|
||||
<span class="detail-value">{{ exc.status | titlecase }}</span>
|
||||
<span class="detail-value">{{ exception()!.status | titlecase }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Severity</span>
|
||||
<span class="detail-value">{{ exc.severity | titlecase }}</span>
|
||||
<span class="detail-value">{{ exception()!.severity | titlecase }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Created</span>
|
||||
<span class="detail-value">{{ formatDate(exc.createdAt) }}</span>
|
||||
<span class="detail-value">{{ formatDate(exception()!.createdAt) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="detail-label">Expires</span>
|
||||
<span class="detail-value">{{ formatDate(exc.timebox.endDate) }}</span>
|
||||
<span class="detail-value">{{ formatDate(exception()!.timebox.endDate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -154,11 +154,11 @@
|
||||
|
||||
<section class="detail-section">
|
||||
<h4 class="section-title">Audit trail</h4>
|
||||
@if ((exc.auditTrail ?? []).length === 0) {
|
||||
@if ((exception()!.auditTrail ?? []).length === 0) {
|
||||
<span class="detail-value">No audit entries available.</span>
|
||||
} @else {
|
||||
<ul class="audit-list">
|
||||
@for (entry of exc.auditTrail ?? []; track entry.auditId) {
|
||||
@for (entry of exception()!.auditTrail ?? []; track entry.auditId) {
|
||||
<li>
|
||||
<span class="detail-label">{{ entry.action }}</span>
|
||||
<span class="detail-value">{{ formatDate(entry.timestamp) }} by {{ entry.actor }}</span>
|
||||
|
||||
143775
src/Web/StellaOps.Web/test_output.txt
Normal file
143775
src/Web/StellaOps.Web/test_output.txt
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user