/** * 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; /// /// Result of processing a batch of inputs. /// public sealed record EvaluationResult { /// /// Whether the evaluation completed successfully. /// public required bool Success { get; init; } /// /// Error message if failed. /// public string? Error { get; init; } /// /// The proof bundle containing all evidence. /// public ProofBundle? ProofBundle { get; init; } /// /// Quick access to disposition results by subject. /// public IReadOnlyDictionary Dispositions { get; init; } = new Dictionary(); /// /// Warnings generated during evaluation. /// public IReadOnlyList Warnings { get; init; } = []; } /// /// Options for trust lattice evaluation. /// public sealed record EvaluationOptions { /// /// Whether to generate a proof bundle. /// public bool GenerateProofBundle { get; init; } = true; /// /// Whether to include full decision traces in the proof bundle. /// public bool IncludeDecisionTraces { get; init; } = true; /// /// Whether to validate claim signatures. /// public bool ValidateSignatures { get; init; } = false; /// /// Timestamp for claim validity evaluation (null = now). /// public DateTimeOffset? EvaluationTime { get; init; } /// /// Filter to specific subjects (null = all). /// public IReadOnlySet? SubjectFilter { get; init; } } /// /// The trust lattice engine orchestrates the complete evaluation pipeline. /// public sealed class TrustLatticeEngine { private readonly PolicyBundle _policy; private readonly LatticeStore _store; private readonly DispositionSelector _selector; private readonly Dictionary _normalizers; /// /// Creates a new trust lattice engine. /// /// The policy bundle to use. public TrustLatticeEngine(PolicyBundle? policy = null) { _policy = policy ?? PolicyBundle.Default; _store = new LatticeStore(); _selector = new DispositionSelector(_policy.GetEffectiveRules()); // Register default normalizers _normalizers = new Dictionary(StringComparer.OrdinalIgnoreCase); RegisterNormalizer(new CycloneDxVexNormalizer()); RegisterNormalizer(new OpenVexNormalizer()); RegisterNormalizer(new CsafVexNormalizer()); } /// /// Gets the policy bundle. /// public PolicyBundle Policy => _policy; /// /// Gets the lattice store. /// public LatticeStore Store => _store; /// /// Registers a VEX normalizer. /// public void RegisterNormalizer(IVexNormalizer normalizer) { _normalizers[normalizer.Format] = normalizer; } /// /// Ingests a claim directly. /// public Claim IngestClaim(Claim claim) { return _store.IngestClaim(claim); } /// /// Ingests multiple claims. /// public IReadOnlyList IngestClaims(IEnumerable claims) { return claims.Select(c => _store.IngestClaim(c)).ToList(); } /// /// Ingests a VEX document. /// /// The VEX document content. /// The VEX format (CycloneDX/ECMA-424, OpenVEX, CSAF). /// The principal making the assertions. /// Default trust label for generated claims. public IReadOnlyList 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); } /// /// Gets the disposition for a subject. /// public DispositionResult GetDisposition(Subject subject) { var state = _store.GetOrCreateSubject(subject); return _selector.Select(state); } /// /// Gets the disposition for a subject by digest. /// public DispositionResult? GetDisposition(string subjectDigest) { var state = _store.GetSubjectState(subjectDigest); if (state is null) return null; return _selector.Select(state); } /// /// Evaluates all subjects and produces dispositions. /// public EvaluationResult Evaluate(EvaluationOptions? options = null) { options ??= new EvaluationOptions(); var warnings = new List(); var dispositions = new Dictionary(); 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, }; } } /// /// Generates a proof bundle for the current evaluation state. /// private ProofBundle GenerateProofBundle( Dictionary 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(); } /// /// Clears all state from the engine. /// public void Clear() { _store.Clear(); } /// /// Gets statistics about the current state. /// public LatticeStoreStats GetStats() => _store.GetStats(); /// /// Creates a builder for claims. /// public ClaimBuilder CreateClaim() => new(this); /// /// Fluent builder for creating and ingesting claims. /// public sealed class ClaimBuilder { private readonly TrustLatticeEngine _engine; private Subject? _subject; private Principal _principal = Principal.Unknown; private TrustLabel? _trustLabel; private readonly List _assertions = []; private readonly List _evidenceRefs = []; internal ClaimBuilder(TrustLatticeEngine engine) { _engine = engine; } /// /// Sets the subject. /// public ClaimBuilder ForSubject(Subject subject) { _subject = subject; return this; } /// /// Sets the principal. /// public ClaimBuilder FromPrincipal(Principal principal) { _principal = principal; return this; } /// /// Sets the trust label. /// public ClaimBuilder WithTrust(TrustLabel label) { _trustLabel = label; return this; } /// /// Asserts an atom value. /// public ClaimBuilder Assert(SecurityAtom atom, bool value, string? justification = null) { _assertions.Add(new AtomAssertion { Atom = atom, Value = value, Justification = justification, }); return this; } /// /// Asserts PRESENT = true. /// public ClaimBuilder Present(bool value = true, string? justification = null) => Assert(SecurityAtom.Present, value, justification); /// /// Asserts APPLIES = true. /// public ClaimBuilder Applies(bool value = true, string? justification = null) => Assert(SecurityAtom.Applies, value, justification); /// /// Asserts REACHABLE = true. /// public ClaimBuilder Reachable(bool value = true, string? justification = null) => Assert(SecurityAtom.Reachable, value, justification); /// /// Asserts MITIGATED = true. /// public ClaimBuilder Mitigated(bool value = true, string? justification = null) => Assert(SecurityAtom.Mitigated, value, justification); /// /// Asserts FIXED = true. /// public ClaimBuilder Fixed(bool value = true, string? justification = null) => Assert(SecurityAtom.Fixed, value, justification); /// /// Asserts MISATTRIBUTED = true. /// public ClaimBuilder Misattributed(bool value = true, string? justification = null) => Assert(SecurityAtom.Misattributed, value, justification); /// /// References evidence by digest. /// public ClaimBuilder WithEvidence(string digest) { _evidenceRefs.Add(digest); return this; } /// /// Builds and ingests the claim. /// 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); } } }