up
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Telemetry.Core.Tests;
|
||||
|
||||
public sealed class ProofCoverageMetricsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 0, 1.0)]
|
||||
[InlineData(0, 10, 0.0)]
|
||||
[InlineData(5, 10, 0.5)]
|
||||
[InlineData(10, 10, 1.0)]
|
||||
public void ComputeCoverageRatio_HandlesZeroDenominator(int withProof, int total, double expected)
|
||||
{
|
||||
var ratio = ProofCoverageMetrics.ComputeCoverageRatio(withProof, total);
|
||||
|
||||
Assert.Equal(expected, ratio, precision: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecordScanCoverage_StoresLatestValues()
|
||||
{
|
||||
using var metrics = new ProofCoverageMetrics();
|
||||
|
||||
metrics.RecordScanCoverage(
|
||||
tenantId: "tenant-1",
|
||||
surfaceId: "surface-1",
|
||||
findingsWithReceipts: 5,
|
||||
totalFindings: 10,
|
||||
vexWithReceipts: 0,
|
||||
totalVex: 0,
|
||||
reachableWithProofs: 2,
|
||||
totalReachable: 4);
|
||||
|
||||
Assert.True(metrics.TryGetLastCoverage("tenant-1", "surface-1", out var coverage));
|
||||
Assert.Equal(0.5, coverage.All, precision: 10);
|
||||
Assert.Equal(1.0, coverage.Vex, precision: 10);
|
||||
Assert.Equal(0.5, coverage.Reachable, precision: 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Telemetry.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for proof coverage tracking.
|
||||
/// Measures ratio of findings / VEX items / reachable findings with valid cryptographic receipts.
|
||||
/// </summary>
|
||||
public sealed class ProofCoverageMetrics : IDisposable
|
||||
{
|
||||
public const string MeterName = "StellaOps.ProofCoverage";
|
||||
|
||||
private readonly Meter _meter;
|
||||
private readonly ConcurrentDictionary<(string TenantId, string SurfaceId), double> _allCoverage = new();
|
||||
private readonly ConcurrentDictionary<(string TenantId, string SurfaceId), double> _vexCoverage = new();
|
||||
private readonly ConcurrentDictionary<(string TenantId, string SurfaceId), double> _reachableCoverage = new();
|
||||
|
||||
private readonly ObservableGauge<double> _proofCoverageAll;
|
||||
private readonly ObservableGauge<double> _proofCoverageVex;
|
||||
private readonly ObservableGauge<double> _proofCoverageReachable;
|
||||
|
||||
private readonly Counter<long> _findingsWithProof;
|
||||
private readonly Counter<long> _findingsWithoutProof;
|
||||
|
||||
public ProofCoverageMetrics(string version = "1.0.0")
|
||||
{
|
||||
_meter = new Meter(MeterName, version);
|
||||
|
||||
_proofCoverageAll = _meter.CreateObservableGauge(
|
||||
name: "stellaops_proof_coverage_all",
|
||||
observeValues: ObserveAllCoverage,
|
||||
unit: "1",
|
||||
description: "Ratio of findings with valid receipts to total findings.");
|
||||
|
||||
_proofCoverageVex = _meter.CreateObservableGauge(
|
||||
name: "stellaops_proof_coverage_vex",
|
||||
observeValues: ObserveVexCoverage,
|
||||
unit: "1",
|
||||
description: "Ratio of VEX items with valid receipts to total VEX items.");
|
||||
|
||||
_proofCoverageReachable = _meter.CreateObservableGauge(
|
||||
name: "stellaops_proof_coverage_reachable",
|
||||
observeValues: ObserveReachableCoverage,
|
||||
unit: "1",
|
||||
description: "Ratio of reachable findings with proofs to total reachable findings.");
|
||||
|
||||
_findingsWithProof = _meter.CreateCounter<long>(
|
||||
name: "stellaops_findings_with_proof_total",
|
||||
unit: "{finding}",
|
||||
description: "Total findings with valid cryptographic proofs.");
|
||||
|
||||
_findingsWithoutProof = _meter.CreateCounter<long>(
|
||||
name: "stellaops_findings_without_proof_total",
|
||||
unit: "{finding}",
|
||||
description: "Total findings without valid cryptographic proofs.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records proof coverage for a completed scan.
|
||||
/// </summary>
|
||||
public void RecordScanCoverage(
|
||||
string tenantId,
|
||||
string surfaceId,
|
||||
int findingsWithReceipts,
|
||||
int totalFindings,
|
||||
int vexWithReceipts,
|
||||
int totalVex,
|
||||
int reachableWithProofs,
|
||||
int totalReachable)
|
||||
{
|
||||
tenantId = NormalizeLabel(tenantId);
|
||||
surfaceId = NormalizeLabel(surfaceId);
|
||||
|
||||
var key = (tenantId, surfaceId);
|
||||
|
||||
var allCoverage = ComputeCoverageRatio(findingsWithReceipts, totalFindings);
|
||||
var vexCoverage = ComputeCoverageRatio(vexWithReceipts, totalVex);
|
||||
var reachableCoverage = ComputeCoverageRatio(reachableWithProofs, totalReachable);
|
||||
|
||||
_allCoverage[key] = allCoverage;
|
||||
_vexCoverage[key] = vexCoverage;
|
||||
_reachableCoverage[key] = reachableCoverage;
|
||||
|
||||
_findingsWithProof.Add(Math.Max(0, findingsWithReceipts),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("proof_type", "receipt"));
|
||||
|
||||
_findingsWithoutProof.Add(Math.Max(0, totalFindings - findingsWithReceipts),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("reason", "missing_receipt"));
|
||||
|
||||
_findingsWithProof.Add(Math.Max(0, vexWithReceipts),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("proof_type", "vex_receipt"));
|
||||
|
||||
_findingsWithoutProof.Add(Math.Max(0, totalVex - vexWithReceipts),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("reason", "missing_vex_receipt"));
|
||||
|
||||
_findingsWithProof.Add(Math.Max(0, reachableWithProofs),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("proof_type", "reachability_proof"));
|
||||
|
||||
_findingsWithoutProof.Add(Math.Max(0, totalReachable - reachableWithProofs),
|
||||
new KeyValuePair<string, object?>("tenant_id", tenantId),
|
||||
new KeyValuePair<string, object?>("reason", "missing_reachability_proof"));
|
||||
}
|
||||
|
||||
public bool TryGetLastCoverage(
|
||||
string tenantId,
|
||||
string surfaceId,
|
||||
out (double All, double Vex, double Reachable) coverage)
|
||||
{
|
||||
coverage = default;
|
||||
|
||||
tenantId = NormalizeLabel(tenantId);
|
||||
surfaceId = NormalizeLabel(surfaceId);
|
||||
|
||||
var key = (tenantId, surfaceId);
|
||||
if (!_allCoverage.TryGetValue(key, out var all))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_vexCoverage.TryGetValue(key, out var vex);
|
||||
_reachableCoverage.TryGetValue(key, out var reachable);
|
||||
coverage = (all, vex, reachable);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static double ComputeCoverageRatio(int withProof, int total)
|
||||
{
|
||||
if (total <= 0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (double)withProof / total;
|
||||
}
|
||||
|
||||
private IEnumerable<Measurement<double>> ObserveAllCoverage() => Observe(_allCoverage);
|
||||
|
||||
private IEnumerable<Measurement<double>> ObserveVexCoverage() => Observe(_vexCoverage);
|
||||
|
||||
private IEnumerable<Measurement<double>> ObserveReachableCoverage() => Observe(_reachableCoverage);
|
||||
|
||||
private static IEnumerable<Measurement<double>> Observe(
|
||||
ConcurrentDictionary<(string TenantId, string SurfaceId), double> values)
|
||||
{
|
||||
foreach (var (key, value) in values)
|
||||
{
|
||||
yield return new Measurement<double>(
|
||||
value,
|
||||
new KeyValuePair<string, object?>("tenant_id", key.TenantId),
|
||||
new KeyValuePair<string, object?>("surface_id", key.SurfaceId));
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeLabel(string? value)
|
||||
=> string.IsNullOrWhiteSpace(value) ? "unknown" : value.Trim();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_meter.Dispose();
|
||||
}
|
||||
}
|
||||
8
src/Telemetry/StellaOps.Telemetry.Core/TASKS.md
Normal file
8
src/Telemetry/StellaOps.Telemetry.Core/TASKS.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Telemetry Core Local Tasks
|
||||
|
||||
This file mirrors sprint work for the Telemetry Core module.
|
||||
|
||||
| Task ID | Sprint | Status | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `DET-3401-005` | `docs/implplan/SPRINT_3401_0001_0001_determinism_scoring_foundations.md` | DONE (2025-12-14) | Added `ProofCoverageMetrics` (`System.Diagnostics.Metrics`) in `src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core/ProofCoverageMetrics.cs` and tests in `src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core.Tests/ProofCoverageMetricsTests.cs`. |
|
||||
|
||||
Reference in New Issue
Block a user