- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips. - Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling. - Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation. - Updated project references to include the new Reachability Drift library.
407 lines
12 KiB
C#
407 lines
12 KiB
C#
/**
|
|
* TrustLatticeEngine - Orchestrates the complete trust evaluation pipeline.
|
|
* Sprint: SPRINT_3600_0001_0001 (Trust Algebra and Lattice Engine)
|
|
* Task: TRUST-016
|
|
*
|
|
* The engine coordinates:
|
|
* 1. VEX normalization from multiple formats
|
|
* 2. Claim ingestion and aggregation
|
|
* 3. K4 lattice evaluation
|
|
* 4. Disposition selection
|
|
* 5. Proof bundle generation
|
|
*/
|
|
|
|
namespace StellaOps.Policy.TrustLattice;
|
|
|
|
/// <summary>
|
|
/// Result of processing a batch of inputs.
|
|
/// </summary>
|
|
public sealed record EvaluationResult
|
|
{
|
|
/// <summary>
|
|
/// Whether the evaluation completed successfully.
|
|
/// </summary>
|
|
public required bool Success { get; init; }
|
|
|
|
/// <summary>
|
|
/// Error message if failed.
|
|
/// </summary>
|
|
public string? Error { get; init; }
|
|
|
|
/// <summary>
|
|
/// The proof bundle containing all evidence.
|
|
/// </summary>
|
|
public ProofBundle? ProofBundle { get; init; }
|
|
|
|
/// <summary>
|
|
/// Quick access to disposition results by subject.
|
|
/// </summary>
|
|
public IReadOnlyDictionary<string, DispositionResult> Dispositions { get; init; } =
|
|
new Dictionary<string, DispositionResult>();
|
|
|
|
/// <summary>
|
|
/// Warnings generated during evaluation.
|
|
/// </summary>
|
|
public IReadOnlyList<string> Warnings { get; init; } = [];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Options for trust lattice evaluation.
|
|
/// </summary>
|
|
public sealed record EvaluationOptions
|
|
{
|
|
/// <summary>
|
|
/// Whether to generate a proof bundle.
|
|
/// </summary>
|
|
public bool GenerateProofBundle { get; init; } = true;
|
|
|
|
/// <summary>
|
|
/// Whether to include full decision traces in the proof bundle.
|
|
/// </summary>
|
|
public bool IncludeDecisionTraces { get; init; } = true;
|
|
|
|
/// <summary>
|
|
/// Whether to validate claim signatures.
|
|
/// </summary>
|
|
public bool ValidateSignatures { get; init; } = false;
|
|
|
|
/// <summary>
|
|
/// Timestamp for claim validity evaluation (null = now).
|
|
/// </summary>
|
|
public DateTimeOffset? EvaluationTime { get; init; }
|
|
|
|
/// <summary>
|
|
/// Filter to specific subjects (null = all).
|
|
/// </summary>
|
|
public IReadOnlySet<string>? SubjectFilter { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The trust lattice engine orchestrates the complete evaluation pipeline.
|
|
/// </summary>
|
|
public sealed class TrustLatticeEngine
|
|
{
|
|
private readonly PolicyBundle _policy;
|
|
private readonly LatticeStore _store;
|
|
private readonly DispositionSelector _selector;
|
|
private readonly Dictionary<string, IVexNormalizer> _normalizers;
|
|
|
|
/// <summary>
|
|
/// Creates a new trust lattice engine.
|
|
/// </summary>
|
|
/// <param name="policy">The policy bundle to use.</param>
|
|
public TrustLatticeEngine(PolicyBundle? policy = null)
|
|
{
|
|
_policy = policy ?? PolicyBundle.Default;
|
|
_store = new LatticeStore();
|
|
_selector = new DispositionSelector(_policy.GetEffectiveRules());
|
|
|
|
// Register default normalizers
|
|
_normalizers = new Dictionary<string, IVexNormalizer>(StringComparer.OrdinalIgnoreCase);
|
|
RegisterNormalizer(new CycloneDxVexNormalizer());
|
|
RegisterNormalizer(new OpenVexNormalizer());
|
|
RegisterNormalizer(new CsafVexNormalizer());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the policy bundle.
|
|
/// </summary>
|
|
public PolicyBundle Policy => _policy;
|
|
|
|
/// <summary>
|
|
/// Gets the lattice store.
|
|
/// </summary>
|
|
public LatticeStore Store => _store;
|
|
|
|
/// <summary>
|
|
/// Registers a VEX normalizer.
|
|
/// </summary>
|
|
public void RegisterNormalizer(IVexNormalizer normalizer)
|
|
{
|
|
_normalizers[normalizer.Format] = normalizer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ingests a claim directly.
|
|
/// </summary>
|
|
public Claim IngestClaim(Claim claim)
|
|
{
|
|
return _store.IngestClaim(claim);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ingests multiple claims.
|
|
/// </summary>
|
|
public IReadOnlyList<Claim> IngestClaims(IEnumerable<Claim> claims)
|
|
{
|
|
return claims.Select(c => _store.IngestClaim(c)).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ingests a VEX document.
|
|
/// </summary>
|
|
/// <param name="document">The VEX document content.</param>
|
|
/// <param name="format">The VEX format (CycloneDX/ECMA-424, OpenVEX, CSAF).</param>
|
|
/// <param name="principal">The principal making the assertions.</param>
|
|
/// <param name="trustLabel">Default trust label for generated claims.</param>
|
|
public IReadOnlyList<Claim> IngestVex(
|
|
string document,
|
|
string format,
|
|
Principal principal,
|
|
TrustLabel? trustLabel = null)
|
|
{
|
|
if (!_normalizers.TryGetValue(format, out var normalizer))
|
|
{
|
|
throw new ArgumentException($"Unknown VEX format: {format}", nameof(format));
|
|
}
|
|
|
|
var claims = normalizer.Normalize(document, principal, trustLabel).ToList();
|
|
return IngestClaims(claims);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the disposition for a subject.
|
|
/// </summary>
|
|
public DispositionResult GetDisposition(Subject subject)
|
|
{
|
|
var state = _store.GetOrCreateSubject(subject);
|
|
return _selector.Select(state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the disposition for a subject by digest.
|
|
/// </summary>
|
|
public DispositionResult? GetDisposition(string subjectDigest)
|
|
{
|
|
var state = _store.GetSubjectState(subjectDigest);
|
|
if (state is null) return null;
|
|
return _selector.Select(state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates all subjects and produces dispositions.
|
|
/// </summary>
|
|
public EvaluationResult Evaluate(EvaluationOptions? options = null)
|
|
{
|
|
options ??= new EvaluationOptions();
|
|
var warnings = new List<string>();
|
|
var dispositions = new Dictionary<string, DispositionResult>();
|
|
|
|
try
|
|
{
|
|
var subjects = _store.GetAllSubjects();
|
|
|
|
// Apply subject filter if specified
|
|
if (options.SubjectFilter is not null)
|
|
{
|
|
subjects = subjects.Where(s => options.SubjectFilter.Contains(s.SubjectDigest));
|
|
}
|
|
|
|
// Evaluate each subject
|
|
foreach (var state in subjects)
|
|
{
|
|
var result = _selector.Select(state);
|
|
dispositions[state.SubjectDigest] = result;
|
|
}
|
|
|
|
// Generate proof bundle if requested
|
|
ProofBundle? proofBundle = null;
|
|
if (options.GenerateProofBundle)
|
|
{
|
|
proofBundle = GenerateProofBundle(dispositions, options);
|
|
}
|
|
|
|
return new EvaluationResult
|
|
{
|
|
Success = true,
|
|
ProofBundle = proofBundle,
|
|
Dispositions = dispositions,
|
|
Warnings = warnings,
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new EvaluationResult
|
|
{
|
|
Success = false,
|
|
Error = ex.Message,
|
|
Dispositions = dispositions,
|
|
Warnings = warnings,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a proof bundle for the current evaluation state.
|
|
/// </summary>
|
|
private ProofBundle GenerateProofBundle(
|
|
Dictionary<string, DispositionResult> dispositions,
|
|
EvaluationOptions options)
|
|
{
|
|
var builder = new ProofBundleBuilder()
|
|
.WithPolicyBundle(_policy);
|
|
|
|
// Add all claims
|
|
foreach (var claim in _store.GetAllClaims())
|
|
{
|
|
builder.AddClaim(claim);
|
|
}
|
|
|
|
// Add atom tables and decisions for each subject
|
|
foreach (var state in _store.GetAllSubjects())
|
|
{
|
|
builder.AddAtomTable(state);
|
|
|
|
if (dispositions.TryGetValue(state.SubjectDigest, out var result))
|
|
{
|
|
builder.AddDecision(state.SubjectDigest, result);
|
|
}
|
|
}
|
|
|
|
return builder.Build();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all state from the engine.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
_store.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets statistics about the current state.
|
|
/// </summary>
|
|
public LatticeStoreStats GetStats() => _store.GetStats();
|
|
|
|
/// <summary>
|
|
/// Creates a builder for claims.
|
|
/// </summary>
|
|
public ClaimBuilder CreateClaim() => new(this);
|
|
|
|
/// <summary>
|
|
/// Fluent builder for creating and ingesting claims.
|
|
/// </summary>
|
|
public sealed class ClaimBuilder
|
|
{
|
|
private readonly TrustLatticeEngine _engine;
|
|
private Subject? _subject;
|
|
private Principal _principal = Principal.Unknown;
|
|
private TrustLabel? _trustLabel;
|
|
private readonly List<AtomAssertion> _assertions = [];
|
|
private readonly List<string> _evidenceRefs = [];
|
|
|
|
internal ClaimBuilder(TrustLatticeEngine engine)
|
|
{
|
|
_engine = engine;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the subject.
|
|
/// </summary>
|
|
public ClaimBuilder ForSubject(Subject subject)
|
|
{
|
|
_subject = subject;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the principal.
|
|
/// </summary>
|
|
public ClaimBuilder FromPrincipal(Principal principal)
|
|
{
|
|
_principal = principal;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the trust label.
|
|
/// </summary>
|
|
public ClaimBuilder WithTrust(TrustLabel label)
|
|
{
|
|
_trustLabel = label;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts an atom value.
|
|
/// </summary>
|
|
public ClaimBuilder Assert(SecurityAtom atom, bool value, string? justification = null)
|
|
{
|
|
_assertions.Add(new AtomAssertion
|
|
{
|
|
Atom = atom,
|
|
Value = value,
|
|
Justification = justification,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts PRESENT = true.
|
|
/// </summary>
|
|
public ClaimBuilder Present(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Present, value, justification);
|
|
|
|
/// <summary>
|
|
/// Asserts APPLIES = true.
|
|
/// </summary>
|
|
public ClaimBuilder Applies(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Applies, value, justification);
|
|
|
|
/// <summary>
|
|
/// Asserts REACHABLE = true.
|
|
/// </summary>
|
|
public ClaimBuilder Reachable(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Reachable, value, justification);
|
|
|
|
/// <summary>
|
|
/// Asserts MITIGATED = true.
|
|
/// </summary>
|
|
public ClaimBuilder Mitigated(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Mitigated, value, justification);
|
|
|
|
/// <summary>
|
|
/// Asserts FIXED = true.
|
|
/// </summary>
|
|
public ClaimBuilder Fixed(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Fixed, value, justification);
|
|
|
|
/// <summary>
|
|
/// Asserts MISATTRIBUTED = true.
|
|
/// </summary>
|
|
public ClaimBuilder Misattributed(bool value = true, string? justification = null)
|
|
=> Assert(SecurityAtom.Misattributed, value, justification);
|
|
|
|
/// <summary>
|
|
/// References evidence by digest.
|
|
/// </summary>
|
|
public ClaimBuilder WithEvidence(string digest)
|
|
{
|
|
_evidenceRefs.Add(digest);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds and ingests the claim.
|
|
/// </summary>
|
|
public Claim Build()
|
|
{
|
|
if (_subject is null)
|
|
throw new InvalidOperationException("Subject is required.");
|
|
|
|
var claim = new Claim
|
|
{
|
|
Subject = _subject,
|
|
Principal = _principal,
|
|
TrustLabel = _trustLabel,
|
|
Assertions = _assertions,
|
|
EvidenceRefs = _evidenceRefs,
|
|
TimeInfo = new ClaimTimeInfo { IssuedAt = DateTimeOffset.UtcNow },
|
|
};
|
|
|
|
return _engine.IngestClaim(claim);
|
|
}
|
|
}
|
|
}
|