old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions

This commit is contained in:
master
2026-01-15 18:37:59 +02:00
parent c631bacee2
commit 88a85cdd92
208 changed files with 32271 additions and 2287 deletions

View File

@@ -235,6 +235,7 @@ builder.Services.AddSingleton<IEvidenceBundleService, EvidenceBundleService>();
// Evidence-Weighted Score services (SPRINT_8200.0012.0004)
builder.Services.AddSingleton<IScoreHistoryStore, InMemoryScoreHistoryStore>();
builder.Services.AddSingleton<IFindingEvidenceProvider, AnchoredFindingEvidenceProvider>();
builder.Services.AddSingleton<IFindingScoringService, FindingScoringService>();
// Webhook services (SPRINT_8200.0012.0004 - Wave 6)

View File

@@ -411,4 +411,16 @@ public sealed record AttestationVerificationResult
public DateTimeOffset? SignedAt { get; init; }
public string? KeyId { get; init; }
public long? RekorLogIndex { get; init; }
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-002)
// Extended anchor metadata fields
/// <summary>Rekor entry ID if transparency-anchored.</summary>
public string? RekorEntryId { get; init; }
/// <summary>Predicate type of the attestation.</summary>
public string? PredicateType { get; init; }
/// <summary>Scope of the attestation (e.g., finding, package, image).</summary>
public string? Scope { get; init; }
}

View File

@@ -0,0 +1,290 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-002)
// Task: Implement IFindingEvidenceProvider to populate anchor metadata
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Signals.EvidenceWeightedScore;
using StellaOps.Signals.EvidenceWeightedScore.Normalizers;
namespace StellaOps.Findings.Ledger.WebService.Services;
/// <summary>
/// Null implementation of IFindingEvidenceProvider that returns no evidence.
/// Use this as a placeholder until real evidence sources are integrated.
/// </summary>
internal sealed class NullFindingEvidenceProvider : IFindingEvidenceProvider
{
public Task<FindingEvidence?> GetEvidenceAsync(string findingId, CancellationToken ct)
=> Task.FromResult<FindingEvidence?>(null);
}
/// <summary>
/// Evidence provider that aggregates from multiple sources and populates anchor metadata.
/// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-002)
/// </summary>
public sealed class AnchoredFindingEvidenceProvider : IFindingEvidenceProvider
{
private readonly IEvidenceRepository _evidenceRepository;
private readonly IAttestationVerifier _attestationVerifier;
private readonly ILogger<AnchoredFindingEvidenceProvider> _logger;
public AnchoredFindingEvidenceProvider(
IEvidenceRepository evidenceRepository,
IAttestationVerifier attestationVerifier,
ILogger<AnchoredFindingEvidenceProvider> logger)
{
_evidenceRepository = evidenceRepository ?? throw new ArgumentNullException(nameof(evidenceRepository));
_attestationVerifier = attestationVerifier ?? throw new ArgumentNullException(nameof(attestationVerifier));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<FindingEvidence?> GetEvidenceAsync(string findingId, CancellationToken ct)
{
// Parse finding ID to extract GUID if needed
if (!TryParseGuid(findingId, out var findingGuid))
{
_logger.LogWarning("Could not parse finding ID {FindingId} as GUID", findingId);
return null;
}
// Get full evidence from repository
var fullEvidence = await _evidenceRepository.GetFullEvidenceAsync(findingGuid, ct).ConfigureAwait(false);
if (fullEvidence is null)
{
_logger.LogDebug("No evidence found for finding {FindingId}", findingId);
return null;
}
// Build anchor metadata from various evidence sources
EvidenceAnchor? reachabilityAnchor = null;
EvidenceAnchor? runtimeAnchor = null;
EvidenceAnchor? vexAnchor = null;
EvidenceAnchor? primaryAnchor = null;
// Check reachability attestation
if (fullEvidence.Reachability?.AttestationDigest is not null)
{
var result = await _attestationVerifier.VerifyAsync(fullEvidence.Reachability.AttestationDigest, ct).ConfigureAwait(false);
reachabilityAnchor = MapToAnchor(result, fullEvidence.Reachability.AttestationDigest);
primaryAnchor ??= reachabilityAnchor;
}
// Check runtime attestations
var latestRuntime = fullEvidence.RuntimeObservations
.Where(r => r.AttestationDigest is not null)
.OrderByDescending(r => r.Timestamp)
.FirstOrDefault();
if (latestRuntime?.AttestationDigest is not null)
{
var result = await _attestationVerifier.VerifyAsync(latestRuntime.AttestationDigest, ct).ConfigureAwait(false);
runtimeAnchor = MapToAnchor(result, latestRuntime.AttestationDigest);
primaryAnchor ??= runtimeAnchor;
}
// Check VEX attestations
var latestVex = fullEvidence.VexStatements
.Where(v => v.AttestationDigest is not null)
.OrderByDescending(v => v.Timestamp)
.FirstOrDefault();
if (latestVex?.AttestationDigest is not null)
{
var result = await _attestationVerifier.VerifyAsync(latestVex.AttestationDigest, ct).ConfigureAwait(false);
vexAnchor = MapToAnchor(result, latestVex.AttestationDigest);
primaryAnchor ??= vexAnchor;
}
// Check policy trace attestation
if (primaryAnchor is null && fullEvidence.PolicyTrace?.AttestationDigest is not null)
{
var result = await _attestationVerifier.VerifyAsync(fullEvidence.PolicyTrace.AttestationDigest, ct).ConfigureAwait(false);
primaryAnchor = MapToAnchor(result, fullEvidence.PolicyTrace.AttestationDigest);
}
return new FindingEvidence
{
FindingId = findingId,
Reachability = MapReachability(fullEvidence, reachabilityAnchor),
Runtime = MapRuntime(fullEvidence, runtimeAnchor),
Backport = null, // Backport evidence not available in FullEvidence yet
Exploit = null, // Exploit evidence not available in FullEvidence yet
SourceTrust = null, // Source trust not available in FullEvidence yet
Mitigations = MapMitigations(fullEvidence),
Anchor = primaryAnchor,
ReachabilityAnchor = reachabilityAnchor,
RuntimeAnchor = runtimeAnchor,
VexAnchor = vexAnchor
};
}
private static EvidenceAnchor MapToAnchor(AttestationVerificationResult result, string digest)
{
if (!result.IsValid)
{
return new EvidenceAnchor
{
Anchored = false
};
}
return new EvidenceAnchor
{
Anchored = true,
EnvelopeDigest = digest,
PredicateType = result.PredicateType,
RekorLogIndex = result.RekorLogIndex,
RekorEntryId = result.RekorEntryId,
Scope = result.Scope,
Verified = result.IsValid,
AttestedAt = result.SignedAt
};
}
private static ReachabilityInput? MapReachability(FullEvidence evidence, EvidenceAnchor? anchor)
{
if (evidence.Reachability is null)
return null;
// Map state string to enum
var state = evidence.Reachability.State switch
{
"reachable" => ReachabilityState.StaticReachable,
"confirmed_reachable" => ReachabilityState.DynamicReachable,
"potentially_reachable" => ReachabilityState.PotentiallyReachable,
"not_reachable" => ReachabilityState.NotReachable,
"unreachable" => ReachabilityState.NotReachable,
_ => ReachabilityState.Unknown
};
// Map anchor to AnchorMetadata if present
AnchorMetadata? anchorMetadata = null;
if (anchor?.Anchored == true)
{
anchorMetadata = new AnchorMetadata
{
IsAnchored = true,
DsseEnvelopeDigest = anchor.EnvelopeDigest,
PredicateType = anchor.PredicateType,
RekorLogIndex = anchor.RekorLogIndex,
RekorEntryId = anchor.RekorEntryId,
AttestationTimestamp = anchor.AttestedAt,
VerificationStatus = anchor.Verified == true ? AnchorVerificationStatus.Verified : AnchorVerificationStatus.Unverified
};
}
return new ReachabilityInput
{
State = state,
Confidence = (double)evidence.Reachability.Confidence,
HopCount = 0, // Not available in current FullEvidence
HasInterproceduralFlow = false,
HasTaintTracking = false,
HasDataFlowSensitivity = false,
EvidenceSource = evidence.Reachability.Issuer,
EvidenceTimestamp = evidence.Reachability.Timestamp,
Anchor = anchorMetadata
};
}
private static RuntimeInput? MapRuntime(FullEvidence evidence, EvidenceAnchor? anchor)
{
if (evidence.RuntimeObservations.Count == 0)
return null;
var latest = evidence.RuntimeObservations
.OrderByDescending(r => r.Timestamp)
.First();
// Calculate recency factor based on observation age
var age = DateTimeOffset.UtcNow - latest.Timestamp;
var recencyFactor = age.TotalHours <= 24 ? 1.0 :
age.TotalDays <= 7 ? 0.7 :
age.TotalDays <= 30 ? 0.4 : 0.1;
// Map anchor to AnchorMetadata if present
AnchorMetadata? anchorMetadata = null;
if (anchor?.Anchored == true)
{
anchorMetadata = new AnchorMetadata
{
IsAnchored = true,
DsseEnvelopeDigest = anchor.EnvelopeDigest,
PredicateType = anchor.PredicateType,
RekorLogIndex = anchor.RekorLogIndex,
RekorEntryId = anchor.RekorEntryId,
AttestationTimestamp = anchor.AttestedAt,
VerificationStatus = anchor.Verified == true ? AnchorVerificationStatus.Verified : AnchorVerificationStatus.Unverified
};
}
return new RuntimeInput
{
Posture = RuntimePosture.ActiveTracing,
ObservationCount = evidence.RuntimeObservations.Count,
LastObservation = latest.Timestamp,
RecencyFactor = recencyFactor,
DirectPathObserved = latest.ObservationType == "direct",
IsProductionTraffic = true, // Assume production unless specified
EvidenceSource = latest.Issuer,
Anchor = anchorMetadata
};
}
private static MitigationInput? MapMitigations(FullEvidence evidence)
{
if (evidence.VexStatements.Count == 0)
return null;
var mitigations = evidence.VexStatements
.Select(v => new ActiveMitigation
{
Type = MitigationType.Unknown, // VEX is not directly in MitigationType enum
Name = $"VEX: {v.Status}",
Effectiveness = v.Status switch
{
"not_affected" => 1.0,
"fixed" => 0.9,
"under_investigation" => 0.3,
"affected" => 0.0,
_ => 0.5
},
Verified = v.AttestationDigest is not null
})
.OrderByDescending(m => m.Effectiveness)
.ThenBy(m => m.Name ?? string.Empty, StringComparer.Ordinal)
.ToList();
var combinedEffectiveness = MitigationInput.CalculateCombinedEffectiveness(mitigations);
var latestVex = evidence.VexStatements.OrderByDescending(v => v.Timestamp).FirstOrDefault();
return new MitigationInput
{
ActiveMitigations = mitigations,
CombinedEffectiveness = combinedEffectiveness,
RuntimeVerified = mitigations.Any(m => m.Verified),
EvidenceTimestamp = latestVex?.Timestamp
};
}
private static bool TryParseGuid(string input, out Guid result)
{
// Handle CVE@PURL format by extracting the GUID portion if present
if (input.Contains('@'))
{
// Try to find a GUID in the string
var parts = input.Split('@', '/', ':');
foreach (var part in parts)
{
if (Guid.TryParse(part, out result))
return true;
}
}
return Guid.TryParse(input, out result);
}
}

View File

@@ -166,10 +166,13 @@ public sealed class FindingScoringService : IFindingScoringService
var now = _timeProvider.GetUtcNow();
var cacheDuration = TimeSpan.FromMinutes(_options.CacheTtlMinutes);
var response = MapToResponse(result, request.IncludeBreakdown, now, cacheDuration);
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
// Pass policy and evidence to MapToResponse for reduction profile and anchor metadata
var response = MapToResponse(result, request.IncludeBreakdown, now, cacheDuration, policy, evidence);
// Cache the result
var cacheKey = GetCacheKey(findingId);
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
// Use cache key that includes policy digest and reduction profile
var cacheKey = GetCacheKey(findingId, policy.ComputeDigest(), policy.AttestedReduction.Enabled);
_cache.Set(cacheKey, response, cacheDuration);
// Record in history
@@ -363,12 +366,69 @@ public sealed class FindingScoringService : IFindingScoringService
private static string GetCacheKey(string findingId) => $"ews:score:{findingId}";
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
// Include policy digest and reduction profile in cache key for determinism
private static string GetCacheKey(string findingId, string policyDigest, bool reductionEnabled)
=> $"ews:score:{findingId}:{policyDigest}:{(reductionEnabled ? "reduction" : "standard")}";
private static EvidenceWeightedScoreResponse MapToResponse(
EvidenceWeightedScoreResult result,
bool includeBreakdown,
DateTimeOffset calculatedAt,
TimeSpan cacheDuration)
TimeSpan cacheDuration,
EvidenceWeightPolicy? policy = null,
FindingEvidence? evidence = null)
{
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
// Extract reduction profile and hard-fail status from flags
var isAttestedReduction = result.Flags.Contains("attested-reduction");
var isHardFail = result.Flags.Contains("hard-fail");
// Determine short-circuit reason from flags/explanations
string? shortCircuitReason = null;
if (result.Flags.Contains("anchored-vex") && result.Score == 0)
{
shortCircuitReason = "anchored_vex_not_affected";
}
else if (isHardFail)
{
shortCircuitReason = "anchored_affected_runtime_confirmed";
}
// Build reduction profile DTO if policy has attested reduction enabled
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
ReductionProfileDto? reductionProfile = null;
if (policy?.AttestedReduction.Enabled == true)
{
var ar = policy.AttestedReduction;
reductionProfile = new ReductionProfileDto
{
Enabled = true,
Mode = ar.HardFailOnAffectedWithRuntime ? "aggressive" : "conservative",
ProfileId = $"attested-{ar.RequiredVerificationStatus.ToString().ToLowerInvariant()}",
MaxReductionPercent = (int)((1.0 - ar.ClampMin) * 100),
RequireVexAnchoring = ar.RequiredVerificationStatus >= AnchorVerificationStatus.Verified,
RequireRekorVerification = ar.RequiredVerificationStatus >= AnchorVerificationStatus.Verified
};
}
// Build anchor DTO from evidence if available
EvidenceAnchorDto? anchorDto = null;
if (evidence?.Anchor is not null && evidence.Anchor.Anchored)
{
anchorDto = new EvidenceAnchorDto
{
Anchored = true,
EnvelopeDigest = evidence.Anchor.EnvelopeDigest,
PredicateType = evidence.Anchor.PredicateType,
RekorLogIndex = evidence.Anchor.RekorLogIndex,
RekorEntryId = evidence.Anchor.RekorEntryId,
Scope = evidence.Anchor.Scope,
Verified = evidence.Anchor.Verified,
AttestedAt = evidence.Anchor.AttestedAt
};
}
return new EvidenceWeightedScoreResponse
{
FindingId = result.FindingId,
@@ -403,7 +463,12 @@ public sealed class FindingScoringService : IFindingScoringService
PolicyDigest = result.PolicyDigest,
CalculatedAt = calculatedAt,
CachedUntil = calculatedAt.Add(cacheDuration),
FromCache = false
FromCache = false,
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-003)
ReductionProfile = reductionProfile,
HardFail = isHardFail,
ShortCircuitReason = shortCircuitReason,
Anchor = anchorDto
};
}

View File

@@ -0,0 +1,352 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) 2025 StellaOps
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-004)
// Task: Unit tests for attested-reduction response fields
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using MsOptions = Microsoft.Extensions.Options;
using Moq;
using StellaOps.Findings.Ledger.WebService.Contracts;
using StellaOps.Findings.Ledger.WebService.Services;
using StellaOps.Signals.EvidenceWeightedScore;
using StellaOps.Signals.EvidenceWeightedScore.Normalizers;
using Xunit;
namespace StellaOps.Findings.Ledger.Tests.Services;
[Trait("Category", "Unit")]
public class FindingScoringServiceTests
{
private readonly Mock<INormalizerAggregator> _normalizer = new();
private readonly Mock<IEvidenceWeightedScoreCalculator> _calculator = new();
private readonly Mock<IEvidenceWeightPolicyProvider> _policyProvider = new();
private readonly Mock<IFindingEvidenceProvider> _evidenceProvider = new();
private readonly Mock<IScoreHistoryStore> _historyStore = new();
private readonly Mock<TimeProvider> _timeProvider = new();
private readonly IMemoryCache _cache;
private readonly FindingScoringService _service;
private readonly DateTimeOffset _now = new(2026, 1, 14, 12, 0, 0, TimeSpan.Zero);
public FindingScoringServiceTests()
{
_cache = new MemoryCache(new MemoryCacheOptions());
var options = MsOptions.Options.Create(new FindingScoringOptions
{
CacheTtlMinutes = 60,
MaxBatchSize = 100,
MaxConcurrency = 10
});
_timeProvider.Setup(tp => tp.GetUtcNow()).Returns(_now);
_service = new FindingScoringService(
_normalizer.Object,
_calculator.Object,
_policyProvider.Object,
_evidenceProvider.Object,
_historyStore.Object,
_cache,
options,
NullLogger<FindingScoringService>.Instance,
_timeProvider.Object);
}
#region Attested Reduction Response Fields Tests
[Fact]
public async Task CalculateScoreAsync_AttestedReductionEnabled_PopulatesReductionProfile()
{
// Arrange
var findingId = "CVE-2024-1234@pkg:npm/lodash@4.17.20";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withAttestedReduction: true);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.ReductionProfile.Should().NotBeNull();
response.ReductionProfile!.Enabled.Should().BeTrue();
response.ReductionProfile.Mode.Should().NotBeNullOrEmpty();
response.ReductionProfile.MaxReductionPercent.Should().BeGreaterThan(0);
}
[Fact]
public async Task CalculateScoreAsync_HardFailTriggered_SetsHardFailTrue()
{
// Arrange
var findingId = "CVE-2024-9999@pkg:npm/critical@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withHardFail: true);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.HardFail.Should().BeTrue();
response.ShortCircuitReason.Should().Be("anchored_affected_runtime_confirmed");
}
[Fact]
public async Task CalculateScoreAsync_AnchoredVexNotAffected_SetsShortCircuitReason()
{
// Arrange
var findingId = "CVE-2024-5555@pkg:npm/not-affected@1.0.0";
var evidence = CreateFindingEvidenceWithAnchor(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withAnchoredVex: true, score: 0);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.Score.Should().Be(0);
response.ShortCircuitReason.Should().Be("anchored_vex_not_affected");
response.HardFail.Should().BeFalse();
}
[Fact]
public async Task CalculateScoreAsync_WithAnchor_PopulatesAnchorDto()
{
// Arrange
var findingId = "CVE-2024-1111@pkg:npm/anchored@2.0.0";
var evidence = CreateFindingEvidenceWithAnchor(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.Anchor.Should().NotBeNull();
response.Anchor!.Anchored.Should().BeTrue();
response.Anchor.EnvelopeDigest.Should().Be("sha256:abc123");
response.Anchor.RekorLogIndex.Should().Be(12345);
}
[Fact]
public async Task CalculateScoreAsync_NoReductionProfile_ReturnsNullReductionProfile()
{
// Arrange
var findingId = "CVE-2024-2222@pkg:npm/standard@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateStandardPolicy(); // No attested reduction
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.ReductionProfile.Should().BeNull();
response.HardFail.Should().BeFalse();
response.ShortCircuitReason.Should().BeNull();
}
[Fact]
public async Task CalculateScoreAsync_NoEvidence_ReturnsNull()
{
// Arrange
var findingId = "CVE-2024-0000@pkg:npm/missing@1.0.0";
_evidenceProvider.Setup(p => p.GetEvidenceAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((FindingEvidence?)null);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest(),
CancellationToken.None);
// Assert
response.Should().BeNull();
}
#endregion
#region Cache Key Tests
[Fact]
public async Task CalculateScoreAsync_DifferentPolicies_UseDifferentCacheKeys()
{
// Arrange
var findingId = "CVE-2024-3333@pkg:npm/cached@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy1 = CreateAttestedReductionPolicy(enabled: true);
var policy2 = CreateAttestedReductionPolicy(enabled: false);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result1 = CreateScoreResult(findingId, withAttestedReduction: true, score: 25);
var result2 = CreateScoreResult(findingId, score: 75);
// First call with reduction enabled
SetupMocks(evidence, policy1, input, result1);
var response1 = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { ForceRecalculate = true },
CancellationToken.None);
// Change policy to disabled
SetupMocks(evidence, policy2, input, result2);
var response2 = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { ForceRecalculate = true },
CancellationToken.None);
// Assert - different scores due to different cache keys
response1!.Score.Should().NotBe(response2!.Score);
}
#endregion
#region Helper Methods
private void SetupMocks(
FindingEvidence evidence,
EvidenceWeightPolicy policy,
EvidenceWeightedScoreInput input,
EvidenceWeightedScoreResult result)
{
_evidenceProvider.Setup(p => p.GetEvidenceAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(evidence);
_policyProvider.Setup(p => p.GetDefaultPolicyAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(policy);
_normalizer.Setup(n => n.Aggregate(It.IsAny<FindingEvidence>()))
.Returns(input);
_calculator.Setup(c => c.Calculate(It.IsAny<EvidenceWeightedScoreInput>(), It.IsAny<EvidenceWeightPolicy>()))
.Returns(result);
}
private static FindingEvidence CreateFindingEvidence(string findingId) => new()
{
FindingId = findingId
};
private static FindingEvidence CreateFindingEvidenceWithAnchor(string findingId) => new()
{
FindingId = findingId,
Anchor = new EvidenceAnchor
{
Anchored = true,
EnvelopeDigest = "sha256:abc123",
PredicateType = "https://stellaops.io/attestation/vex/v1",
RekorLogIndex = 12345,
RekorEntryId = "entry-123",
Scope = "finding",
Verified = true,
AttestedAt = DateTimeOffset.UtcNow.AddHours(-1)
}
};
private static EvidenceWeightedScoreInput CreateEvidenceWeightedScoreInput(string findingId) => new()
{
FindingId = findingId,
Rch = 0.5,
Rts = 0.3,
Bkp = 0.0,
Xpl = 0.4,
Src = 0.6,
Mit = 0.1
};
private static EvidenceWeightPolicy CreateAttestedReductionPolicy(bool enabled) => new()
{
Version = "1.0.0",
Profile = "test",
CreatedAt = DateTimeOffset.UtcNow,
Weights = EvidenceWeights.Default,
Guardrails = GuardrailConfig.Default,
Buckets = BucketThresholds.Default,
AttestedReduction = enabled
? AttestedReductionConfig.EnabledDefault
: AttestedReductionConfig.Default
};
private static EvidenceWeightPolicy CreateStandardPolicy() => new()
{
Version = "1.0.0",
Profile = "standard",
CreatedAt = DateTimeOffset.UtcNow,
Weights = EvidenceWeights.Default,
Guardrails = GuardrailConfig.Default,
Buckets = BucketThresholds.Default,
AttestedReduction = AttestedReductionConfig.Default
};
private EvidenceWeightedScoreResult CreateScoreResult(
string findingId,
bool withAttestedReduction = false,
bool withHardFail = false,
bool withAnchoredVex = false,
int score = 50)
{
var flags = new List<string>();
if (withAttestedReduction) flags.Add("attested-reduction");
if (withHardFail)
{
flags.Add("hard-fail");
flags.Add("anchored-vex");
flags.Add("anchored-runtime");
}
if (withAnchoredVex)
{
flags.Add("anchored-vex");
flags.Add("vendor-na");
}
return new EvidenceWeightedScoreResult
{
FindingId = findingId,
Score = score,
Bucket = score >= 90 ? ScoreBucket.ActNow :
score >= 70 ? ScoreBucket.ScheduleNext :
score >= 40 ? ScoreBucket.Investigate : ScoreBucket.Watchlist,
Inputs = new EvidenceInputValues(0.5, 0.3, 0.0, 0.4, 0.6, 0.1),
Weights = EvidenceWeights.Default,
Breakdown = [],
Flags = flags,
Explanations = ["Test explanation"],
Caps = AppliedGuardrails.None(score),
PolicyDigest = "sha256:policy123",
CalculatedAt = _now
};
}
#endregion
}