save progress
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2024-2026 StellaOps Contributors.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Proof;
|
||||
|
||||
namespace StellaOps.VexLens.Propagation;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the propagation rule engine.
|
||||
/// </summary>
|
||||
public sealed class PropagationRuleEngine : IPropagationRuleEngine
|
||||
{
|
||||
private readonly ImmutableArray<PropagationRule> _rules;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PropagationRuleEngine with default rules.
|
||||
/// </summary>
|
||||
public PropagationRuleEngine() : this(GetDefaultRules())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PropagationRuleEngine with specified rules.
|
||||
/// </summary>
|
||||
public PropagationRuleEngine(IEnumerable<PropagationRule> rules)
|
||||
{
|
||||
_rules = rules.OrderBy(r => r.Priority).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PropagationResult Propagate(
|
||||
ComponentVerdict componentVerdict,
|
||||
IDependencyGraph graph,
|
||||
PropagationPolicy policy)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(componentVerdict);
|
||||
ArgumentNullException.ThrowIfNull(graph);
|
||||
ArgumentNullException.ThrowIfNull(policy);
|
||||
|
||||
var ruleResults = new List<PropagationRuleResult>();
|
||||
var analyzedPaths = new List<DependencyPath>();
|
||||
VexStatus? inheritedStatus = null;
|
||||
var overrideApplied = false;
|
||||
string? overrideReason = null;
|
||||
var anyTriggered = false;
|
||||
|
||||
// Analyze dependency paths
|
||||
var paths = graph.GetPathsTo(componentVerdict.ComponentKey);
|
||||
foreach (var path in paths)
|
||||
{
|
||||
// Skip excluded scopes
|
||||
if (policy.ExcludedScopes.Contains(path.Scope))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if beyond max depth
|
||||
if (path.Depth > policy.MaxTransitiveDepth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
analyzedPaths.Add(path);
|
||||
}
|
||||
|
||||
// Evaluate rules in priority order
|
||||
foreach (var rule in _rules)
|
||||
{
|
||||
var result = rule.Evaluate(componentVerdict, graph, policy);
|
||||
ruleResults.Add(result);
|
||||
|
||||
if (result.Triggered)
|
||||
{
|
||||
anyTriggered = true;
|
||||
|
||||
// First triggered rule with an effect wins
|
||||
if (inheritedStatus is null && !string.IsNullOrEmpty(result.Effect))
|
||||
{
|
||||
// Parse effect to determine inherited status
|
||||
if (result.Effect.Contains("affected", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inheritedStatus = VexStatus.Affected;
|
||||
}
|
||||
else if (result.Effect.Contains("not_affected", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inheritedStatus = VexStatus.NotAffected;
|
||||
}
|
||||
else if (result.Effect.Contains("fixed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inheritedStatus = VexStatus.Fixed;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for override
|
||||
if (result.Effect?.Contains("override", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
overrideApplied = true;
|
||||
overrideReason = result.Effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PropagationResult(
|
||||
anyTriggered,
|
||||
ruleResults.ToImmutableArray(),
|
||||
analyzedPaths.ToImmutableArray(),
|
||||
inheritedStatus,
|
||||
overrideApplied,
|
||||
overrideReason);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<PropagationRule> GetRules() => _rules;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default set of propagation rules.
|
||||
/// </summary>
|
||||
public static IEnumerable<PropagationRule> GetDefaultRules()
|
||||
{
|
||||
yield return new DirectDependencyAffectedRule();
|
||||
yield return new TransitiveDependencyRule();
|
||||
yield return new DependencyFixedRule();
|
||||
yield return new DependencyNotAffectedRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rule: If direct dependency is affected, product inherits affected unless overridden.
|
||||
/// </summary>
|
||||
public sealed class DirectDependencyAffectedRule : PropagationRule
|
||||
{
|
||||
public override string RuleId => "direct-dependency-affected";
|
||||
public override string Description => "If direct dependency is affected, product inherits affected unless product-level override";
|
||||
public override int Priority => 10;
|
||||
|
||||
public override PropagationRuleResult Evaluate(
|
||||
ComponentVerdict verdict,
|
||||
IDependencyGraph graph,
|
||||
PropagationPolicy policy)
|
||||
{
|
||||
if (!policy.InheritAffectedFromDirectDependency)
|
||||
{
|
||||
return new PropagationRuleResult(RuleId, Description, false, null, []);
|
||||
}
|
||||
|
||||
// Check if any direct dependency is affected
|
||||
var directDeps = graph.GetDirectDependencies(verdict.ComponentKey).ToList();
|
||||
var affectedComponents = new List<string>();
|
||||
|
||||
foreach (var dep in directDeps)
|
||||
{
|
||||
if (dep.PathType == DependencyPathType.DirectDependency)
|
||||
{
|
||||
// In a real implementation, we would look up the verdict for the dependency
|
||||
// For now, we track the dependency for potential impact
|
||||
affectedComponents.Add(dep.To);
|
||||
}
|
||||
}
|
||||
|
||||
// This rule triggers when the component's own verdict is affected and it has direct dependencies
|
||||
var triggered = verdict.Status == VexStatus.Affected && affectedComponents.Count > 0;
|
||||
|
||||
return new PropagationRuleResult(
|
||||
RuleId,
|
||||
Description,
|
||||
triggered,
|
||||
triggered ? "Product inherits affected status from direct dependency" : null,
|
||||
affectedComponents.ToImmutableArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rule: If transitive dependency is affected, flag for review but don't auto-inherit.
|
||||
/// </summary>
|
||||
public sealed class TransitiveDependencyRule : PropagationRule
|
||||
{
|
||||
public override string RuleId => "transitive-dependency-affected";
|
||||
public override string Description => "If transitive dependency is affected, flag for review but don't auto-inherit";
|
||||
public override int Priority => 20;
|
||||
|
||||
public override PropagationRuleResult Evaluate(
|
||||
ComponentVerdict verdict,
|
||||
IDependencyGraph graph,
|
||||
PropagationPolicy policy)
|
||||
{
|
||||
if (!policy.EnableTransitivePropagation)
|
||||
{
|
||||
return new PropagationRuleResult(RuleId, Description, false, null, []);
|
||||
}
|
||||
|
||||
var paths = graph.GetPathsTo(verdict.ComponentKey).ToList();
|
||||
var transitivePaths = paths
|
||||
.Where(p => p.PathType == DependencyPathType.TransitiveDependency)
|
||||
.Where(p => p.Depth <= policy.MaxTransitiveDepth)
|
||||
.ToList();
|
||||
|
||||
var triggered = verdict.Status == VexStatus.Affected && transitivePaths.Count > 0;
|
||||
var affectedComponents = transitivePaths.Select(p => p.Root).Distinct().ToImmutableArray();
|
||||
|
||||
return new PropagationRuleResult(
|
||||
RuleId,
|
||||
Description,
|
||||
triggered,
|
||||
triggered ? "Transitive dependency is affected - flagged for review" : null,
|
||||
affectedComponents);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rule: If dependency was affected but is now fixed, allow product NotAffected if vulnerable code was removed.
|
||||
/// </summary>
|
||||
public sealed class DependencyFixedRule : PropagationRule
|
||||
{
|
||||
public override string RuleId => "dependency-fixed";
|
||||
public override string Description => "If dependency was affected but is now fixed, allow product NotAffected if vulnerable code was removed";
|
||||
public override int Priority => 30;
|
||||
|
||||
public override PropagationRuleResult Evaluate(
|
||||
ComponentVerdict verdict,
|
||||
IDependencyGraph graph,
|
||||
PropagationPolicy policy)
|
||||
{
|
||||
// This rule triggers when a dependency is now fixed
|
||||
var triggered = verdict.Status == VexStatus.Fixed;
|
||||
|
||||
return new PropagationRuleResult(
|
||||
RuleId,
|
||||
Description,
|
||||
triggered,
|
||||
triggered ? "Dependency is fixed - product may be not_affected with override" : null,
|
||||
[]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rule: If dependency is not_affected, product may inherit if dependency is leaf.
|
||||
/// </summary>
|
||||
public sealed class DependencyNotAffectedRule : PropagationRule
|
||||
{
|
||||
public override string RuleId => "dependency-not-affected";
|
||||
public override string Description => "If dependency is not_affected, product may inherit if dependency is leaf";
|
||||
public override int Priority => 40;
|
||||
|
||||
public override PropagationRuleResult Evaluate(
|
||||
ComponentVerdict verdict,
|
||||
IDependencyGraph graph,
|
||||
PropagationPolicy policy)
|
||||
{
|
||||
if (!policy.InheritNotAffectedFromLeafDependency)
|
||||
{
|
||||
return new PropagationRuleResult(RuleId, Description, false, null, []);
|
||||
}
|
||||
|
||||
var isLeaf = graph.IsLeaf(verdict.ComponentKey);
|
||||
var triggered = verdict.Status == VexStatus.NotAffected && isLeaf;
|
||||
|
||||
return new PropagationRuleResult(
|
||||
RuleId,
|
||||
Description,
|
||||
triggered,
|
||||
triggered ? "Leaf dependency is not_affected - dependents may inherit" : null,
|
||||
[]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user