feat: Implement air-gap functionality with timeline impact and evidence snapshot services
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

- Added AirgapTimelineImpact, AirgapTimelineImpactInput, and AirgapTimelineImpactResult records for managing air-gap bundle import impacts.
- Introduced EvidenceSnapshotRecord, EvidenceSnapshotLinkInput, and EvidenceSnapshotLinkResult records for linking findings to evidence snapshots.
- Created IEvidenceSnapshotRepository interface for managing evidence snapshot records.
- Developed StalenessValidationService to validate staleness and enforce freshness thresholds.
- Implemented AirgapTimelineService for emitting timeline events related to bundle imports.
- Added EvidenceSnapshotService for linking findings to evidence snapshots and verifying their validity.
- Introduced AirGapOptions for configuring air-gap staleness enforcement and thresholds.
- Added minimal jsPDF stub for offline/testing builds in the web application.
- Created TypeScript definitions for jsPDF to enhance type safety in the web application.
This commit is contained in:
StellaOps Bot
2025-12-06 01:30:08 +02:00
parent 6c1177a6ce
commit 2eaf0f699b
144 changed files with 7578 additions and 2581 deletions

View File

@@ -59,6 +59,21 @@ internal static class LedgerMetrics
"ledger_attachments_encryption_failures_total",
description: "Count of attachment encryption/signing/upload failures.");
private static readonly Histogram<double> AirgapStalenessSeconds = Meter.CreateHistogram<double>(
"ledger_airgap_staleness_seconds",
unit: "s",
description: "Current staleness of air-gap imported data by domain.");
private static readonly Counter<long> StalenessValidationFailures = Meter.CreateCounter<long>(
"ledger_staleness_validation_failures_total",
description: "Count of staleness validation failures blocking exports.");
private static readonly ObservableGauge<double> AirgapStalenessGauge =
Meter.CreateObservableGauge("ledger_airgap_staleness_gauge_seconds", ObserveAirgapStaleness, unit: "s",
description: "Current staleness of air-gap data by domain.");
private static readonly ConcurrentDictionary<string, double> AirgapStalenessByDomain = new(StringComparer.Ordinal);
private static readonly ObservableGauge<double> ProjectionLagGauge =
Meter.CreateObservableGauge("ledger_projection_lag_seconds", ObserveProjectionLag, unit: "s",
description: "Lag between ledger recorded_at and projection application time.");
@@ -228,6 +243,27 @@ internal static class LedgerMetrics
public static void RecordProjectionLag(TimeSpan lag, string? tenantId) =>
UpdateProjectionLag(tenantId, lag.TotalSeconds);
public static void RecordAirgapStaleness(string? domainId, long stalenessSeconds)
{
var key = string.IsNullOrWhiteSpace(domainId) ? "unknown" : domainId;
var tags = new KeyValuePair<string, object?>[]
{
new("domain", key)
};
AirgapStalenessSeconds.Record(stalenessSeconds, tags);
AirgapStalenessByDomain[key] = stalenessSeconds;
}
public static void RecordStalenessValidationFailure(string? domainId)
{
var key = string.IsNullOrWhiteSpace(domainId) ? "unknown" : domainId;
var tags = new KeyValuePair<string, object?>[]
{
new("domain", key)
};
StalenessValidationFailures.Add(1, tags);
}
private static IEnumerable<Measurement<double>> ObserveProjectionLag()
{
foreach (var kvp in ProjectionLagByTenant)
@@ -267,6 +303,14 @@ internal static class LedgerMetrics
new KeyValuePair<string, object?>("git_sha", GitSha));
}
private static IEnumerable<Measurement<double>> ObserveAirgapStaleness()
{
foreach (var kvp in AirgapStalenessByDomain)
{
yield return new Measurement<double>(kvp.Value, new KeyValuePair<string, object?>("domain", kvp.Key));
}
}
private static string NormalizeRole(string role) => string.IsNullOrWhiteSpace(role) ? "unspecified" : role.ToLowerInvariant();
private static string NormalizeTenant(string? tenantId) => string.IsNullOrWhiteSpace(tenantId) ? string.Empty : tenantId;