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