Implement VEX document verification system with issuer management and signature verification
- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation. - Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments. - Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats. - Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats. - Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction. - Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
This commit is contained in:
152
src/VexLens/StellaOps.VexLens/Trust/ITrustWeightEngine.cs
Normal file
152
src/VexLens/StellaOps.VexLens/Trust/ITrustWeightEngine.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Verification;
|
||||
|
||||
namespace StellaOps.VexLens.Trust;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for computing trust weights for VEX statements.
|
||||
/// </summary>
|
||||
public interface ITrustWeightEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the trust weight for a VEX statement.
|
||||
/// </summary>
|
||||
Task<TrustWeightResult> ComputeWeightAsync(
|
||||
TrustWeightRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Computes trust weights for multiple statements in batch.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<TrustWeightResult>> ComputeWeightsBatchAsync(
|
||||
IEnumerable<TrustWeightRequest> requests,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current trust weight configuration.
|
||||
/// </summary>
|
||||
TrustWeightConfiguration GetConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the trust weight configuration.
|
||||
/// </summary>
|
||||
void UpdateConfiguration(TrustWeightConfiguration configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for trust weight computation.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightRequest(
|
||||
NormalizedStatement Statement,
|
||||
VexIssuer? Issuer,
|
||||
SignatureVerificationResult? SignatureVerification,
|
||||
DateTimeOffset? DocumentIssuedAt,
|
||||
TrustWeightContext Context);
|
||||
|
||||
/// <summary>
|
||||
/// Context for trust weight computation.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightContext(
|
||||
string? TenantId,
|
||||
DateTimeOffset EvaluationTime,
|
||||
IReadOnlyDictionary<string, object?>? CustomFactors);
|
||||
|
||||
/// <summary>
|
||||
/// Result of trust weight computation.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightResult(
|
||||
NormalizedStatement Statement,
|
||||
double Weight,
|
||||
TrustWeightBreakdown Breakdown,
|
||||
IReadOnlyList<TrustWeightFactor> Factors,
|
||||
IReadOnlyList<string> Warnings);
|
||||
|
||||
/// <summary>
|
||||
/// Breakdown of trust weight by component.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightBreakdown(
|
||||
double IssuerWeight,
|
||||
double SignatureWeight,
|
||||
double FreshnessWeight,
|
||||
double SourceFormatWeight,
|
||||
double StatusSpecificityWeight,
|
||||
double CustomWeight);
|
||||
|
||||
/// <summary>
|
||||
/// Individual factor contributing to trust weight.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightFactor(
|
||||
string FactorId,
|
||||
string Name,
|
||||
double RawValue,
|
||||
double WeightedValue,
|
||||
double Multiplier,
|
||||
string? Reason);
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for trust weight computation.
|
||||
/// </summary>
|
||||
public sealed record TrustWeightConfiguration(
|
||||
IssuerTrustWeights IssuerWeights,
|
||||
SignatureTrustWeights SignatureWeights,
|
||||
FreshnessTrustWeights FreshnessWeights,
|
||||
SourceFormatWeights SourceFormatWeights,
|
||||
StatusSpecificityWeights StatusSpecificityWeights,
|
||||
double MinimumWeight,
|
||||
double MaximumWeight);
|
||||
|
||||
/// <summary>
|
||||
/// Trust weights based on issuer category and tier.
|
||||
/// </summary>
|
||||
public sealed record IssuerTrustWeights(
|
||||
double VendorMultiplier,
|
||||
double DistributorMultiplier,
|
||||
double CommunityMultiplier,
|
||||
double InternalMultiplier,
|
||||
double AggregatorMultiplier,
|
||||
double UnknownIssuerMultiplier,
|
||||
double AuthoritativeTierBonus,
|
||||
double TrustedTierBonus,
|
||||
double UntrustedTierPenalty);
|
||||
|
||||
/// <summary>
|
||||
/// Trust weights based on signature verification.
|
||||
/// </summary>
|
||||
public sealed record SignatureTrustWeights(
|
||||
double ValidSignatureMultiplier,
|
||||
double InvalidSignaturePenalty,
|
||||
double NoSignaturePenalty,
|
||||
double ExpiredCertificatePenalty,
|
||||
double RevokedCertificatePenalty,
|
||||
double TimestampedBonus);
|
||||
|
||||
/// <summary>
|
||||
/// Trust weights based on document freshness.
|
||||
/// </summary>
|
||||
public sealed record FreshnessTrustWeights(
|
||||
TimeSpan FreshThreshold,
|
||||
TimeSpan StaleThreshold,
|
||||
TimeSpan ExpiredThreshold,
|
||||
double FreshMultiplier,
|
||||
double StaleMultiplier,
|
||||
double ExpiredMultiplier);
|
||||
|
||||
/// <summary>
|
||||
/// Trust weights based on source format.
|
||||
/// </summary>
|
||||
public sealed record SourceFormatWeights(
|
||||
double OpenVexMultiplier,
|
||||
double CsafVexMultiplier,
|
||||
double CycloneDxVexMultiplier,
|
||||
double SpdxVexMultiplier,
|
||||
double StellaOpsMultiplier);
|
||||
|
||||
/// <summary>
|
||||
/// Trust weights based on status specificity.
|
||||
/// </summary>
|
||||
public sealed record StatusSpecificityWeights(
|
||||
double NotAffectedBonus,
|
||||
double FixedBonus,
|
||||
double AffectedNeutral,
|
||||
double UnderInvestigationPenalty,
|
||||
double JustificationBonus);
|
||||
445
src/VexLens/StellaOps.VexLens/Trust/TrustWeightEngine.cs
Normal file
445
src/VexLens/StellaOps.VexLens/Trust/TrustWeightEngine.cs
Normal file
@@ -0,0 +1,445 @@
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Verification;
|
||||
|
||||
namespace StellaOps.VexLens.Trust;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ITrustWeightEngine"/>.
|
||||
/// Computes trust weights based on issuer, signature, freshness, and other factors.
|
||||
/// </summary>
|
||||
public sealed class TrustWeightEngine : ITrustWeightEngine
|
||||
{
|
||||
private TrustWeightConfiguration _configuration;
|
||||
|
||||
public TrustWeightEngine(TrustWeightConfiguration? configuration = null)
|
||||
{
|
||||
_configuration = configuration ?? CreateDefaultConfiguration();
|
||||
}
|
||||
|
||||
public Task<TrustWeightResult> ComputeWeightAsync(
|
||||
TrustWeightRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var factors = new List<TrustWeightFactor>();
|
||||
var warnings = new List<string>();
|
||||
|
||||
// Compute issuer weight
|
||||
var issuerWeight = ComputeIssuerWeight(request.Issuer, factors);
|
||||
|
||||
// Compute signature weight
|
||||
var signatureWeight = ComputeSignatureWeight(request.SignatureVerification, factors);
|
||||
|
||||
// Compute freshness weight
|
||||
var freshnessWeight = ComputeFreshnessWeight(
|
||||
request.DocumentIssuedAt,
|
||||
request.Statement.FirstSeen,
|
||||
request.Context.EvaluationTime,
|
||||
factors);
|
||||
|
||||
// Compute source format weight
|
||||
var sourceFormatWeight = ComputeSourceFormatWeight(request.Statement, factors);
|
||||
|
||||
// Compute status specificity weight
|
||||
var statusWeight = ComputeStatusSpecificityWeight(request.Statement, factors);
|
||||
|
||||
// Compute custom weight
|
||||
var customWeight = ComputeCustomWeight(request.Context.CustomFactors, factors);
|
||||
|
||||
// Combine weights
|
||||
var breakdown = new TrustWeightBreakdown(
|
||||
IssuerWeight: issuerWeight,
|
||||
SignatureWeight: signatureWeight,
|
||||
FreshnessWeight: freshnessWeight,
|
||||
SourceFormatWeight: sourceFormatWeight,
|
||||
StatusSpecificityWeight: statusWeight,
|
||||
CustomWeight: customWeight);
|
||||
|
||||
var combinedWeight = CombineWeights(breakdown);
|
||||
|
||||
// Clamp to configured range
|
||||
var finalWeight = Math.Clamp(combinedWeight, _configuration.MinimumWeight, _configuration.MaximumWeight);
|
||||
|
||||
if (finalWeight != combinedWeight)
|
||||
{
|
||||
warnings.Add($"Weight clamped from {combinedWeight:F4} to {finalWeight:F4}");
|
||||
}
|
||||
|
||||
return Task.FromResult(new TrustWeightResult(
|
||||
Statement: request.Statement,
|
||||
Weight: finalWeight,
|
||||
Breakdown: breakdown,
|
||||
Factors: factors,
|
||||
Warnings: warnings));
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TrustWeightResult>> ComputeWeightsBatchAsync(
|
||||
IEnumerable<TrustWeightRequest> requests,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var results = new List<TrustWeightResult>();
|
||||
|
||||
foreach (var request in requests)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var result = await ComputeWeightAsync(request, cancellationToken);
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public TrustWeightConfiguration GetConfiguration() => _configuration;
|
||||
|
||||
public void UpdateConfiguration(TrustWeightConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private double ComputeIssuerWeight(VexIssuer? issuer, List<TrustWeightFactor> factors)
|
||||
{
|
||||
var config = _configuration.IssuerWeights;
|
||||
|
||||
if (issuer == null)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "issuer_unknown",
|
||||
Name: "Unknown Issuer",
|
||||
RawValue: 0.0,
|
||||
WeightedValue: config.UnknownIssuerMultiplier,
|
||||
Multiplier: config.UnknownIssuerMultiplier,
|
||||
Reason: "No issuer information available"));
|
||||
|
||||
return config.UnknownIssuerMultiplier;
|
||||
}
|
||||
|
||||
// Base weight from category
|
||||
var categoryMultiplier = issuer.Category switch
|
||||
{
|
||||
IssuerCategory.Vendor => config.VendorMultiplier,
|
||||
IssuerCategory.Distributor => config.DistributorMultiplier,
|
||||
IssuerCategory.Community => config.CommunityMultiplier,
|
||||
IssuerCategory.Internal => config.InternalMultiplier,
|
||||
IssuerCategory.Aggregator => config.AggregatorMultiplier,
|
||||
_ => config.UnknownIssuerMultiplier
|
||||
};
|
||||
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "issuer_category",
|
||||
Name: $"Issuer Category: {issuer.Category}",
|
||||
RawValue: 1.0,
|
||||
WeightedValue: categoryMultiplier,
|
||||
Multiplier: categoryMultiplier,
|
||||
Reason: $"Category '{issuer.Category}' has multiplier {categoryMultiplier:F2}"));
|
||||
|
||||
// Trust tier adjustment
|
||||
var tierAdjustment = issuer.TrustTier switch
|
||||
{
|
||||
TrustTier.Authoritative => config.AuthoritativeTierBonus,
|
||||
TrustTier.Trusted => config.TrustedTierBonus,
|
||||
TrustTier.Untrusted => config.UntrustedTierPenalty,
|
||||
_ => 0.0
|
||||
};
|
||||
|
||||
if (Math.Abs(tierAdjustment) > 0.001)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "issuer_tier",
|
||||
Name: $"Trust Tier: {issuer.TrustTier}",
|
||||
RawValue: tierAdjustment,
|
||||
WeightedValue: tierAdjustment,
|
||||
Multiplier: 1.0,
|
||||
Reason: $"Trust tier '{issuer.TrustTier}' adjustment: {tierAdjustment:+0.00;-0.00}"));
|
||||
}
|
||||
|
||||
return categoryMultiplier + tierAdjustment;
|
||||
}
|
||||
|
||||
private double ComputeSignatureWeight(SignatureVerificationResult? verification, List<TrustWeightFactor> factors)
|
||||
{
|
||||
var config = _configuration.SignatureWeights;
|
||||
|
||||
if (verification == null)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "signature_none",
|
||||
Name: "No Signature",
|
||||
RawValue: 0.0,
|
||||
WeightedValue: config.NoSignaturePenalty,
|
||||
Multiplier: config.NoSignaturePenalty,
|
||||
Reason: "Document has no signature or signature not verified"));
|
||||
|
||||
return config.NoSignaturePenalty;
|
||||
}
|
||||
|
||||
double weight;
|
||||
string reason;
|
||||
|
||||
switch (verification.Status)
|
||||
{
|
||||
case SignatureVerificationStatus.Valid:
|
||||
weight = config.ValidSignatureMultiplier;
|
||||
reason = "Signature is valid and verified";
|
||||
break;
|
||||
|
||||
case SignatureVerificationStatus.InvalidSignature:
|
||||
weight = config.InvalidSignaturePenalty;
|
||||
reason = "Signature verification failed";
|
||||
break;
|
||||
|
||||
case SignatureVerificationStatus.ExpiredCertificate:
|
||||
weight = config.ExpiredCertificatePenalty;
|
||||
reason = "Certificate has expired";
|
||||
break;
|
||||
|
||||
case SignatureVerificationStatus.RevokedCertificate:
|
||||
weight = config.RevokedCertificatePenalty;
|
||||
reason = "Certificate has been revoked";
|
||||
break;
|
||||
|
||||
case SignatureVerificationStatus.UntrustedIssuer:
|
||||
weight = config.NoSignaturePenalty;
|
||||
reason = "Signature from untrusted issuer";
|
||||
break;
|
||||
|
||||
default:
|
||||
weight = config.NoSignaturePenalty;
|
||||
reason = $"Signature status: {verification.Status}";
|
||||
break;
|
||||
}
|
||||
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "signature_status",
|
||||
Name: $"Signature: {verification.Status}",
|
||||
RawValue: verification.IsValid ? 1.0 : 0.0,
|
||||
WeightedValue: weight,
|
||||
Multiplier: weight,
|
||||
Reason: reason));
|
||||
|
||||
// Timestamp bonus
|
||||
if (verification.IsValid && verification.Timestamp?.IsValid == true)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "signature_timestamped",
|
||||
Name: "Timestamped Signature",
|
||||
RawValue: 1.0,
|
||||
WeightedValue: config.TimestampedBonus,
|
||||
Multiplier: 1.0,
|
||||
Reason: $"Signature has valid timestamp from {verification.Timestamp.TimestampAuthority}"));
|
||||
|
||||
weight += config.TimestampedBonus;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
private double ComputeFreshnessWeight(
|
||||
DateTimeOffset? documentIssuedAt,
|
||||
DateTimeOffset? statementFirstSeen,
|
||||
DateTimeOffset evaluationTime,
|
||||
List<TrustWeightFactor> factors)
|
||||
{
|
||||
var config = _configuration.FreshnessWeights;
|
||||
var referenceTime = documentIssuedAt ?? statementFirstSeen;
|
||||
|
||||
if (!referenceTime.HasValue)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "freshness_unknown",
|
||||
Name: "Unknown Age",
|
||||
RawValue: 0.0,
|
||||
WeightedValue: config.StaleMultiplier,
|
||||
Multiplier: config.StaleMultiplier,
|
||||
Reason: "No timestamp available to determine freshness"));
|
||||
|
||||
return config.StaleMultiplier;
|
||||
}
|
||||
|
||||
var age = evaluationTime - referenceTime.Value;
|
||||
double weight;
|
||||
string category;
|
||||
|
||||
if (age < config.FreshThreshold)
|
||||
{
|
||||
weight = config.FreshMultiplier;
|
||||
category = "Fresh";
|
||||
}
|
||||
else if (age < config.StaleThreshold)
|
||||
{
|
||||
weight = config.StaleMultiplier;
|
||||
category = "Stale";
|
||||
}
|
||||
else
|
||||
{
|
||||
weight = config.ExpiredMultiplier;
|
||||
category = "Expired";
|
||||
}
|
||||
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "freshness",
|
||||
Name: $"Freshness: {category}",
|
||||
RawValue: age.TotalDays,
|
||||
WeightedValue: weight,
|
||||
Multiplier: weight,
|
||||
Reason: $"Document age: {FormatAge(age)} ({category})"));
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
private double ComputeSourceFormatWeight(NormalizedStatement statement, List<TrustWeightFactor> factors)
|
||||
{
|
||||
// Note: We don't have direct access to source format from statement
|
||||
// This would typically come from the document context
|
||||
// For now, return neutral weight
|
||||
var config = _configuration.SourceFormatWeights;
|
||||
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "source_format",
|
||||
Name: "Source Format",
|
||||
RawValue: 1.0,
|
||||
WeightedValue: 1.0,
|
||||
Multiplier: 1.0,
|
||||
Reason: "Source format weight applied at document level"));
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
private double ComputeStatusSpecificityWeight(NormalizedStatement statement, List<TrustWeightFactor> factors)
|
||||
{
|
||||
var config = _configuration.StatusSpecificityWeights;
|
||||
|
||||
var statusWeight = statement.Status switch
|
||||
{
|
||||
VexStatus.NotAffected => config.NotAffectedBonus,
|
||||
VexStatus.Fixed => config.FixedBonus,
|
||||
VexStatus.Affected => config.AffectedNeutral,
|
||||
VexStatus.UnderInvestigation => config.UnderInvestigationPenalty,
|
||||
_ => 0.0
|
||||
};
|
||||
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "status",
|
||||
Name: $"Status: {statement.Status}",
|
||||
RawValue: 1.0,
|
||||
WeightedValue: statusWeight,
|
||||
Multiplier: 1.0,
|
||||
Reason: $"Status '{statement.Status}' weight adjustment"));
|
||||
|
||||
// Justification bonus for not_affected
|
||||
if (statement.Status == VexStatus.NotAffected && statement.Justification.HasValue)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: "justification",
|
||||
Name: $"Justification: {statement.Justification}",
|
||||
RawValue: 1.0,
|
||||
WeightedValue: config.JustificationBonus,
|
||||
Multiplier: 1.0,
|
||||
Reason: $"Has justification: {statement.Justification}"));
|
||||
|
||||
statusWeight += config.JustificationBonus;
|
||||
}
|
||||
|
||||
return statusWeight;
|
||||
}
|
||||
|
||||
private double ComputeCustomWeight(
|
||||
IReadOnlyDictionary<string, object?>? customFactors,
|
||||
List<TrustWeightFactor> factors)
|
||||
{
|
||||
if (customFactors == null || customFactors.Count == 0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double totalCustomWeight = 0.0;
|
||||
|
||||
foreach (var (key, value) in customFactors)
|
||||
{
|
||||
if (value is double d)
|
||||
{
|
||||
factors.Add(new TrustWeightFactor(
|
||||
FactorId: $"custom_{key}",
|
||||
Name: $"Custom: {key}",
|
||||
RawValue: d,
|
||||
WeightedValue: d,
|
||||
Multiplier: 1.0,
|
||||
Reason: $"Custom factor '{key}'"));
|
||||
|
||||
totalCustomWeight += d;
|
||||
}
|
||||
}
|
||||
|
||||
return totalCustomWeight;
|
||||
}
|
||||
|
||||
private double CombineWeights(TrustWeightBreakdown breakdown)
|
||||
{
|
||||
// Multiplicative combination with additive adjustments
|
||||
var baseWeight = breakdown.IssuerWeight * breakdown.SignatureWeight * breakdown.FreshnessWeight;
|
||||
var adjustments = breakdown.StatusSpecificityWeight + breakdown.CustomWeight;
|
||||
|
||||
return baseWeight + adjustments;
|
||||
}
|
||||
|
||||
private static string FormatAge(TimeSpan age)
|
||||
{
|
||||
if (age.TotalDays >= 365)
|
||||
{
|
||||
return $"{age.TotalDays / 365:F1} years";
|
||||
}
|
||||
|
||||
if (age.TotalDays >= 30)
|
||||
{
|
||||
return $"{age.TotalDays / 30:F1} months";
|
||||
}
|
||||
|
||||
if (age.TotalDays >= 1)
|
||||
{
|
||||
return $"{age.TotalDays:F1} days";
|
||||
}
|
||||
|
||||
return $"{age.TotalHours:F1} hours";
|
||||
}
|
||||
|
||||
public static TrustWeightConfiguration CreateDefaultConfiguration()
|
||||
{
|
||||
return new TrustWeightConfiguration(
|
||||
IssuerWeights: new IssuerTrustWeights(
|
||||
VendorMultiplier: 1.0,
|
||||
DistributorMultiplier: 0.9,
|
||||
CommunityMultiplier: 0.7,
|
||||
InternalMultiplier: 0.8,
|
||||
AggregatorMultiplier: 0.6,
|
||||
UnknownIssuerMultiplier: 0.3,
|
||||
AuthoritativeTierBonus: 0.2,
|
||||
TrustedTierBonus: 0.1,
|
||||
UntrustedTierPenalty: -0.3),
|
||||
SignatureWeights: new SignatureTrustWeights(
|
||||
ValidSignatureMultiplier: 1.0,
|
||||
InvalidSignaturePenalty: 0.1,
|
||||
NoSignaturePenalty: 0.5,
|
||||
ExpiredCertificatePenalty: 0.3,
|
||||
RevokedCertificatePenalty: 0.1,
|
||||
TimestampedBonus: 0.1),
|
||||
FreshnessWeights: new FreshnessTrustWeights(
|
||||
FreshThreshold: TimeSpan.FromDays(7),
|
||||
StaleThreshold: TimeSpan.FromDays(90),
|
||||
ExpiredThreshold: TimeSpan.FromDays(365),
|
||||
FreshMultiplier: 1.0,
|
||||
StaleMultiplier: 0.8,
|
||||
ExpiredMultiplier: 0.5),
|
||||
SourceFormatWeights: new SourceFormatWeights(
|
||||
OpenVexMultiplier: 1.0,
|
||||
CsafVexMultiplier: 1.0,
|
||||
CycloneDxVexMultiplier: 0.95,
|
||||
SpdxVexMultiplier: 0.9,
|
||||
StellaOpsMultiplier: 1.0),
|
||||
StatusSpecificityWeights: new StatusSpecificityWeights(
|
||||
NotAffectedBonus: 0.1,
|
||||
FixedBonus: 0.05,
|
||||
AffectedNeutral: 0.0,
|
||||
UnderInvestigationPenalty: -0.1,
|
||||
JustificationBonus: 0.1),
|
||||
MinimumWeight: 0.0,
|
||||
MaximumWeight: 1.5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user