Files
git.stella-ops.org/src/Policy/__Libraries/StellaOps.Policy/TrustLattice/TrustLatticeEngine.cs
StellaOps Bot 5fc469ad98 feat: Add VEX Status Chip component and integration tests for reachability drift detection
- 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.
2025-12-20 01:26:42 +02:00

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);
}
}
}