DET-004: Refactor Policy library for determinism - Gates, Snapshots, TrustLattice, Scoring, Explanation
- VexProofGate: Inject TimeProvider for proof age validation - SnapshotBuilder: Inject TimeProvider for WithVex/WithSbom/WithReachability/Build - CsafVexNormalizer, OpenVexNormalizer, VexNormalizers: Add optional issuedAt parameter - TrustLatticeEngine.ClaimBuilder: Add optional issuedAt parameter to Build - PolicyBundle: Add asOf parameter to IsTrusted and GetMaxAssurance - ProofLedger: Add createdAtUtc parameter to ToJson - ScoreAttestationBuilder: Add scoredAt parameter to Create - ScoringRulesSnapshotBuilder: Add createdAt parameter to Create - TrustSourceWeightService: Inject TimeProvider for stale data calculation - PolicyExplanation.Create: Add evaluatedAt parameter - PolicyExplanationRecord.FromExplanation: Add recordId and evaluatedAt parameters - PolicyPreviewService: Inject TimeProvider for snapshot creation - PolicySnapshotStore: Inject IGuidProvider for audit entry ID generation
This commit is contained in:
@@ -104,6 +104,7 @@ public sealed record VexProofGateContext
|
|||||||
public sealed class VexProofGate : IPolicyGate
|
public sealed class VexProofGate : IPolicyGate
|
||||||
{
|
{
|
||||||
private readonly VexProofGateOptions _options;
|
private readonly VexProofGateOptions _options;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
// Confidence tier ordering for comparison
|
// Confidence tier ordering for comparison
|
||||||
private static readonly IReadOnlyDictionary<string, int> ConfidenceTierOrder =
|
private static readonly IReadOnlyDictionary<string, int> ConfidenceTierOrder =
|
||||||
@@ -114,9 +115,10 @@ public sealed class VexProofGate : IPolicyGate
|
|||||||
["high"] = 3,
|
["high"] = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
public VexProofGate(VexProofGateOptions? options = null)
|
public VexProofGate(VexProofGateOptions? options = null, TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_options = options ?? new VexProofGateOptions();
|
_options = options ?? new VexProofGateOptions();
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<GateResult> EvaluateAsync(
|
public Task<GateResult> EvaluateAsync(
|
||||||
@@ -207,7 +209,7 @@ public sealed class VexProofGate : IPolicyGate
|
|||||||
// Validate proof age
|
// Validate proof age
|
||||||
if (_options.MaxProofAgeHours >= 0 && proofContext.ProofComputedAt.HasValue)
|
if (_options.MaxProofAgeHours >= 0 && proofContext.ProofComputedAt.HasValue)
|
||||||
{
|
{
|
||||||
var proofAge = DateTimeOffset.UtcNow - proofContext.ProofComputedAt.Value;
|
var proofAge = _timeProvider.GetUtcNow() - proofContext.ProofComputedAt.Value;
|
||||||
details["proofAgeHours"] = proofAge.TotalHours;
|
details["proofAgeHours"] = proofAge.TotalHours;
|
||||||
details["maxProofAgeHours"] = _options.MaxProofAgeHours;
|
details["maxProofAgeHours"] = _options.MaxProofAgeHours;
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,16 @@ public sealed record PolicyExplanation(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an explanation with full context for persistence.
|
/// Creates an explanation with full context for persistence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="findingId">The finding ID.</param>
|
||||||
|
/// <param name="decision">The policy decision.</param>
|
||||||
|
/// <param name="ruleName">The rule name.</param>
|
||||||
|
/// <param name="reason">The reason for the decision.</param>
|
||||||
|
/// <param name="nodes">The explanation nodes.</param>
|
||||||
|
/// <param name="ruleHits">Optional rule hits.</param>
|
||||||
|
/// <param name="inputs">Optional evaluated inputs.</param>
|
||||||
|
/// <param name="policyVersion">Optional policy version.</param>
|
||||||
|
/// <param name="correlationId">Optional correlation ID.</param>
|
||||||
|
/// <param name="evaluatedAt">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
public static PolicyExplanation Create(
|
public static PolicyExplanation Create(
|
||||||
string findingId,
|
string findingId,
|
||||||
PolicyVerdictStatus decision,
|
PolicyVerdictStatus decision,
|
||||||
@@ -70,12 +80,13 @@ public sealed record PolicyExplanation(
|
|||||||
IEnumerable<RuleHit>? ruleHits = null,
|
IEnumerable<RuleHit>? ruleHits = null,
|
||||||
IDictionary<string, object?>? inputs = null,
|
IDictionary<string, object?>? inputs = null,
|
||||||
string? policyVersion = null,
|
string? policyVersion = null,
|
||||||
string? correlationId = null) =>
|
string? correlationId = null,
|
||||||
|
DateTimeOffset? evaluatedAt = null) =>
|
||||||
new(findingId, decision, ruleName, reason, nodes.ToImmutableArray())
|
new(findingId, decision, ruleName, reason, nodes.ToImmutableArray())
|
||||||
{
|
{
|
||||||
RuleHits = ruleHits?.ToImmutableArray() ?? ImmutableArray<RuleHit>.Empty,
|
RuleHits = ruleHits?.ToImmutableArray() ?? ImmutableArray<RuleHit>.Empty,
|
||||||
EvaluatedInputs = inputs?.ToImmutableDictionary() ?? ImmutableDictionary<string, object?>.Empty,
|
EvaluatedInputs = inputs?.ToImmutableDictionary() ?? ImmutableDictionary<string, object?>.Empty,
|
||||||
EvaluatedAt = DateTimeOffset.UtcNow,
|
EvaluatedAt = evaluatedAt ?? DateTimeOffset.UtcNow,
|
||||||
PolicyVersion = policyVersion,
|
PolicyVersion = policyVersion,
|
||||||
CorrelationId = correlationId
|
CorrelationId = correlationId
|
||||||
};
|
};
|
||||||
@@ -214,13 +225,21 @@ public sealed record PolicyExplanationRecord(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a persistence record from an explanation.
|
/// Creates a persistence record from an explanation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="explanation">The explanation to convert.</param>
|
||||||
|
/// <param name="policyId">The policy ID.</param>
|
||||||
|
/// <param name="tenantId">Optional tenant identifier.</param>
|
||||||
|
/// <param name="actor">Optional actor who triggered the evaluation.</param>
|
||||||
|
/// <param name="recordId">Optional record ID for deterministic testing. If null, generates a new GUID.</param>
|
||||||
|
/// <param name="evaluatedAt">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
public static PolicyExplanationRecord FromExplanation(
|
public static PolicyExplanationRecord FromExplanation(
|
||||||
PolicyExplanation explanation,
|
PolicyExplanation explanation,
|
||||||
string policyId,
|
string policyId,
|
||||||
string? tenantId = null,
|
string? tenantId = null,
|
||||||
string? actor = null)
|
string? actor = null,
|
||||||
|
string? recordId = null,
|
||||||
|
DateTimeOffset? evaluatedAt = null)
|
||||||
{
|
{
|
||||||
var id = $"pexp-{Guid.NewGuid():N}";
|
var id = recordId ?? $"pexp-{Guid.NewGuid():N}";
|
||||||
var ruleHitsJson = System.Text.Json.JsonSerializer.Serialize(explanation.RuleHits);
|
var ruleHitsJson = System.Text.Json.JsonSerializer.Serialize(explanation.RuleHits);
|
||||||
var inputsJson = System.Text.Json.JsonSerializer.Serialize(explanation.EvaluatedInputs);
|
var inputsJson = System.Text.Json.JsonSerializer.Serialize(explanation.EvaluatedInputs);
|
||||||
var treeJson = System.Text.Json.JsonSerializer.Serialize(explanation.Nodes);
|
var treeJson = System.Text.Json.JsonSerializer.Serialize(explanation.Nodes);
|
||||||
@@ -235,7 +254,7 @@ public sealed record PolicyExplanationRecord(
|
|||||||
RuleHitsJson: ruleHitsJson,
|
RuleHitsJson: ruleHitsJson,
|
||||||
InputsJson: inputsJson,
|
InputsJson: inputsJson,
|
||||||
ExplanationTreeJson: treeJson,
|
ExplanationTreeJson: treeJson,
|
||||||
EvaluatedAt: explanation.EvaluatedAt ?? DateTimeOffset.UtcNow,
|
EvaluatedAt: explanation.EvaluatedAt ?? evaluatedAt ?? DateTimeOffset.UtcNow,
|
||||||
CorrelationId: explanation.CorrelationId,
|
CorrelationId: explanation.CorrelationId,
|
||||||
TenantId: tenantId,
|
TenantId: tenantId,
|
||||||
Actor: actor);
|
Actor: actor);
|
||||||
|
|||||||
@@ -13,11 +13,16 @@ public sealed class PolicyPreviewService
|
|||||||
{
|
{
|
||||||
private readonly PolicySnapshotStore _snapshotStore;
|
private readonly PolicySnapshotStore _snapshotStore;
|
||||||
private readonly ILogger<PolicyPreviewService> _logger;
|
private readonly ILogger<PolicyPreviewService> _logger;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public PolicyPreviewService(PolicySnapshotStore snapshotStore, ILogger<PolicyPreviewService> logger)
|
public PolicyPreviewService(
|
||||||
|
PolicySnapshotStore snapshotStore,
|
||||||
|
ILogger<PolicyPreviewService> logger,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_snapshotStore = snapshotStore ?? throw new ArgumentNullException(nameof(snapshotStore));
|
_snapshotStore = snapshotStore ?? throw new ArgumentNullException(nameof(snapshotStore));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PolicyPreviewResponse> PreviewAsync(PolicyPreviewRequest request, CancellationToken cancellationToken = default)
|
public async Task<PolicyPreviewResponse> PreviewAsync(PolicyPreviewRequest request, CancellationToken cancellationToken = default)
|
||||||
@@ -59,7 +64,7 @@ public sealed class PolicyPreviewService
|
|||||||
request.SnapshotOverride?.RevisionNumber + 1 ?? 0,
|
request.SnapshotOverride?.RevisionNumber + 1 ?? 0,
|
||||||
request.SnapshotOverride?.RevisionId ?? "preview",
|
request.SnapshotOverride?.RevisionId ?? "preview",
|
||||||
digest,
|
digest,
|
||||||
DateTimeOffset.UtcNow,
|
_timeProvider.GetUtcNow(),
|
||||||
request.ProposedPolicy.Actor,
|
request.ProposedPolicy.Actor,
|
||||||
request.ProposedPolicy.Format,
|
request.ProposedPolicy.Format,
|
||||||
binding.Document,
|
binding.Document,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using StellaOps.Determinism;
|
||||||
|
|
||||||
namespace StellaOps.Policy;
|
namespace StellaOps.Policy;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ public sealed class PolicySnapshotStore
|
|||||||
private readonly IPolicySnapshotRepository _snapshotRepository;
|
private readonly IPolicySnapshotRepository _snapshotRepository;
|
||||||
private readonly IPolicyAuditRepository _auditRepository;
|
private readonly IPolicyAuditRepository _auditRepository;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
|
private readonly IGuidProvider _guidProvider;
|
||||||
private readonly ILogger<PolicySnapshotStore> _logger;
|
private readonly ILogger<PolicySnapshotStore> _logger;
|
||||||
private readonly SemaphoreSlim _mutex = new(1, 1);
|
private readonly SemaphoreSlim _mutex = new(1, 1);
|
||||||
|
|
||||||
@@ -17,11 +19,13 @@ public sealed class PolicySnapshotStore
|
|||||||
IPolicySnapshotRepository snapshotRepository,
|
IPolicySnapshotRepository snapshotRepository,
|
||||||
IPolicyAuditRepository auditRepository,
|
IPolicyAuditRepository auditRepository,
|
||||||
TimeProvider? timeProvider,
|
TimeProvider? timeProvider,
|
||||||
|
IGuidProvider? guidProvider,
|
||||||
ILogger<PolicySnapshotStore> logger)
|
ILogger<PolicySnapshotStore> logger)
|
||||||
{
|
{
|
||||||
_snapshotRepository = snapshotRepository ?? throw new ArgumentNullException(nameof(snapshotRepository));
|
_snapshotRepository = snapshotRepository ?? throw new ArgumentNullException(nameof(snapshotRepository));
|
||||||
_auditRepository = auditRepository ?? throw new ArgumentNullException(nameof(auditRepository));
|
_auditRepository = auditRepository ?? throw new ArgumentNullException(nameof(auditRepository));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
|
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ public sealed class PolicySnapshotStore
|
|||||||
|
|
||||||
var auditMessage = content.Description ?? "Policy snapshot created";
|
var auditMessage = content.Description ?? "Policy snapshot created";
|
||||||
var auditEntry = new PolicyAuditEntry(
|
var auditEntry = new PolicyAuditEntry(
|
||||||
Guid.NewGuid(),
|
_guidProvider.NewGuid(),
|
||||||
createdAt,
|
createdAt,
|
||||||
"snapshot.created",
|
"snapshot.created",
|
||||||
revisionId,
|
revisionId,
|
||||||
|
|||||||
@@ -118,15 +118,16 @@ public sealed class ProofLedger
|
|||||||
/// Serialize the ledger to JSON.
|
/// Serialize the ledger to JSON.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">Optional JSON serializer options.</param>
|
/// <param name="options">Optional JSON serializer options.</param>
|
||||||
|
/// <param name="createdAtUtc">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
/// <returns>The JSON representation of the ledger.</returns>
|
/// <returns>The JSON representation of the ledger.</returns>
|
||||||
public string ToJson(JsonSerializerOptions? options = null)
|
public string ToJson(JsonSerializerOptions? options = null, DateTimeOffset? createdAtUtc = null)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
var payload = new ProofLedgerPayload(
|
var payload = new ProofLedgerPayload(
|
||||||
Nodes: [.. _nodes],
|
Nodes: [.. _nodes],
|
||||||
RootHash: RootHash(),
|
RootHash: RootHash(),
|
||||||
CreatedAtUtc: DateTimeOffset.UtcNow);
|
CreatedAtUtc: createdAtUtc ?? DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
return JsonSerializer.Serialize(payload, options ?? DefaultJsonOptions);
|
return JsonSerializer.Serialize(payload, options ?? DefaultJsonOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,17 +320,25 @@ public sealed class ScoreAttestationBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new builder.
|
/// Creates a new builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="subjectDigest">The subject digest.</param>
|
||||||
|
/// <param name="overallScore">The overall score.</param>
|
||||||
|
/// <param name="confidence">The confidence value.</param>
|
||||||
|
/// <param name="breakdown">The score breakdown.</param>
|
||||||
|
/// <param name="policy">The scoring policy reference.</param>
|
||||||
|
/// <param name="inputs">The scoring inputs.</param>
|
||||||
|
/// <param name="scoredAt">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
public static ScoreAttestationBuilder Create(
|
public static ScoreAttestationBuilder Create(
|
||||||
string subjectDigest,
|
string subjectDigest,
|
||||||
int overallScore,
|
int overallScore,
|
||||||
double confidence,
|
double confidence,
|
||||||
ScoreBreakdown breakdown,
|
ScoreBreakdown breakdown,
|
||||||
ScoringPolicyRef policy,
|
ScoringPolicyRef policy,
|
||||||
ScoringInputs inputs)
|
ScoringInputs inputs,
|
||||||
|
DateTimeOffset? scoredAt = null)
|
||||||
{
|
{
|
||||||
return new ScoreAttestationBuilder(new ScoreAttestationStatement
|
return new ScoreAttestationBuilder(new ScoreAttestationStatement
|
||||||
{
|
{
|
||||||
ScoredAt = DateTimeOffset.UtcNow,
|
ScoredAt = scoredAt ?? DateTimeOffset.UtcNow,
|
||||||
SubjectDigest = subjectDigest,
|
SubjectDigest = subjectDigest,
|
||||||
OverallScore = overallScore,
|
OverallScore = overallScore,
|
||||||
Confidence = confidence,
|
Confidence = confidence,
|
||||||
|
|||||||
@@ -346,13 +346,16 @@ public sealed class ScoringRulesSnapshotBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new builder with defaults.
|
/// Creates a new builder with defaults.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ScoringRulesSnapshotBuilder Create(string id, int version)
|
/// <param name="id">The snapshot ID.</param>
|
||||||
|
/// <param name="version">The snapshot version.</param>
|
||||||
|
/// <param name="createdAt">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
|
public static ScoringRulesSnapshotBuilder Create(string id, int version, DateTimeOffset? createdAt = null)
|
||||||
{
|
{
|
||||||
return new ScoringRulesSnapshotBuilder(new ScoringRulesSnapshot
|
return new ScoringRulesSnapshotBuilder(new ScoringRulesSnapshot
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Version = version,
|
Version = version,
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
CreatedAt = createdAt ?? DateTimeOffset.UtcNow,
|
||||||
Digest = "", // Will be computed on build
|
Digest = "", // Will be computed on build
|
||||||
Weights = new ScoringWeights(),
|
Weights = new ScoringWeights(),
|
||||||
Thresholds = new GradeThresholds(),
|
Thresholds = new GradeThresholds(),
|
||||||
|
|||||||
@@ -281,10 +281,12 @@ public sealed record WeightedMergeResult
|
|||||||
public sealed class TrustSourceWeightService
|
public sealed class TrustSourceWeightService
|
||||||
{
|
{
|
||||||
private readonly TrustSourceWeightConfig _config;
|
private readonly TrustSourceWeightConfig _config;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public TrustSourceWeightService(TrustSourceWeightConfig? config = null)
|
public TrustSourceWeightService(TrustSourceWeightConfig? config = null, TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_config = config ?? new TrustSourceWeightConfig();
|
_config = config ?? new TrustSourceWeightConfig();
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -320,7 +322,7 @@ public sealed class TrustSourceWeightService
|
|||||||
// Penalty for stale data (>7 days old)
|
// Penalty for stale data (>7 days old)
|
||||||
if (source.FetchedAt.HasValue)
|
if (source.FetchedAt.HasValue)
|
||||||
{
|
{
|
||||||
var age = DateTimeOffset.UtcNow - source.FetchedAt.Value;
|
var age = _timeProvider.GetUtcNow() - source.FetchedAt.Value;
|
||||||
if (age.TotalDays > 7)
|
if (age.TotalDays > 7)
|
||||||
{
|
{
|
||||||
weight *= 0.95;
|
weight *= 0.95;
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ public sealed class SnapshotBuilder
|
|||||||
private TrustBundleRef? _trust;
|
private TrustBundleRef? _trust;
|
||||||
private DeterminismProfile? _environment;
|
private DeterminismProfile? _environment;
|
||||||
private readonly ICryptoHash _cryptoHash;
|
private readonly ICryptoHash _cryptoHash;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public SnapshotBuilder(ICryptoHash cryptoHash)
|
public SnapshotBuilder(ICryptoHash cryptoHash, TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
||||||
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SnapshotBuilder WithEngine(string name, string version, string commit)
|
public SnapshotBuilder WithEngine(string name, string version, string commit)
|
||||||
@@ -81,7 +83,7 @@ public sealed class SnapshotBuilder
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = KnowledgeSourceTypes.Vex,
|
Type = KnowledgeSourceTypes.Vex,
|
||||||
Epoch = DateTimeOffset.UtcNow.ToString("o", CultureInfo.InvariantCulture),
|
Epoch = _timeProvider.GetUtcNow().ToString("o", CultureInfo.InvariantCulture),
|
||||||
Digest = digest,
|
Digest = digest,
|
||||||
Origin = origin
|
Origin = origin
|
||||||
});
|
});
|
||||||
@@ -94,7 +96,7 @@ public sealed class SnapshotBuilder
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = KnowledgeSourceTypes.Sbom,
|
Type = KnowledgeSourceTypes.Sbom,
|
||||||
Epoch = DateTimeOffset.UtcNow.ToString("o", CultureInfo.InvariantCulture),
|
Epoch = _timeProvider.GetUtcNow().ToString("o", CultureInfo.InvariantCulture),
|
||||||
Digest = digest,
|
Digest = digest,
|
||||||
Origin = origin
|
Origin = origin
|
||||||
});
|
});
|
||||||
@@ -107,7 +109,7 @@ public sealed class SnapshotBuilder
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Type = KnowledgeSourceTypes.Reachability,
|
Type = KnowledgeSourceTypes.Reachability,
|
||||||
Epoch = DateTimeOffset.UtcNow.ToString("o", CultureInfo.InvariantCulture),
|
Epoch = _timeProvider.GetUtcNow().ToString("o", CultureInfo.InvariantCulture),
|
||||||
Digest = digest,
|
Digest = digest,
|
||||||
Origin = origin
|
Origin = origin
|
||||||
});
|
});
|
||||||
@@ -148,7 +150,7 @@ public sealed class SnapshotBuilder
|
|||||||
var manifest = new KnowledgeSnapshotManifest
|
var manifest = new KnowledgeSnapshotManifest
|
||||||
{
|
{
|
||||||
SnapshotId = "", // Placeholder
|
SnapshotId = "", // Placeholder
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
CreatedAt = _timeProvider.GetUtcNow(),
|
||||||
Engine = _engine,
|
Engine = _engine,
|
||||||
Plugins = _plugins.ToList(),
|
Plugins = _plugins.ToList(),
|
||||||
Policy = _policy,
|
Policy = _policy,
|
||||||
|
|||||||
@@ -186,7 +186,8 @@ public sealed class CsafVexNormalizer : IVexNormalizer
|
|||||||
CsafFlagLabel flag = CsafFlagLabel.None,
|
CsafFlagLabel flag = CsafFlagLabel.None,
|
||||||
string? remediation = null,
|
string? remediation = null,
|
||||||
Principal? principal = null,
|
Principal? principal = null,
|
||||||
TrustLabel? trustLabel = null)
|
TrustLabel? trustLabel = null,
|
||||||
|
DateTimeOffset? issuedAt = null)
|
||||||
{
|
{
|
||||||
var assertions = new List<AtomAssertion>();
|
var assertions = new List<AtomAssertion>();
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ public sealed class CsafVexNormalizer : IVexNormalizer
|
|||||||
Issuer = principal ?? Principal.Unknown,
|
Issuer = principal ?? Principal.Unknown,
|
||||||
Assertions = assertions,
|
Assertions = assertions,
|
||||||
TrustLabel = trustLabel,
|
TrustLabel = trustLabel,
|
||||||
Time = new ClaimTimeInfo { IssuedAt = DateTimeOffset.UtcNow },
|
Time = new ClaimTimeInfo { IssuedAt = issuedAt ?? DateTimeOffset.UtcNow },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
|||||||
string? actionStatement = null,
|
string? actionStatement = null,
|
||||||
string? impactStatement = null,
|
string? impactStatement = null,
|
||||||
Principal? principal = null,
|
Principal? principal = null,
|
||||||
TrustLabel? trustLabel = null)
|
TrustLabel? trustLabel = null,
|
||||||
|
DateTimeOffset? issuedAt = null)
|
||||||
{
|
{
|
||||||
var assertions = new List<AtomAssertion>();
|
var assertions = new List<AtomAssertion>();
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
|||||||
Issuer = principal ?? Principal.Unknown,
|
Issuer = principal ?? Principal.Unknown,
|
||||||
Assertions = assertions,
|
Assertions = assertions,
|
||||||
TrustLabel = trustLabel,
|
TrustLabel = trustLabel,
|
||||||
Time = new ClaimTimeInfo { IssuedAt = DateTimeOffset.UtcNow },
|
Time = new ClaimTimeInfo { IssuedAt = issuedAt ?? DateTimeOffset.UtcNow },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,9 +235,12 @@ public sealed record PolicyBundle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a principal is trusted for a given scope.
|
/// Checks if a principal is trusted for a given scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsTrusted(Principal principal, AuthorityScope? requiredScope = null)
|
/// <param name="principal">The principal to check.</param>
|
||||||
|
/// <param name="requiredScope">Optional required authority scope.</param>
|
||||||
|
/// <param name="asOf">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
|
public bool IsTrusted(Principal principal, AuthorityScope? requiredScope = null, DateTimeOffset? asOf = null)
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = asOf ?? DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
foreach (var root in TrustRoots)
|
foreach (var root in TrustRoots)
|
||||||
{
|
{
|
||||||
@@ -257,9 +260,11 @@ public sealed record PolicyBundle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum assurance level for a principal.
|
/// Gets the maximum assurance level for a principal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AssuranceLevel? GetMaxAssurance(Principal principal)
|
/// <param name="principal">The principal to check.</param>
|
||||||
|
/// <param name="asOf">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
|
public AssuranceLevel? GetMaxAssurance(Principal principal, DateTimeOffset? asOf = null)
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = asOf ?? DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
foreach (var root in TrustRoots)
|
foreach (var root in TrustRoots)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -397,7 +397,8 @@ public sealed class TrustLatticeEngine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds and ingests the claim.
|
/// Builds and ingests the claim.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Claim Build()
|
/// <param name="issuedAt">Optional timestamp for deterministic testing. If null, uses current time.</param>
|
||||||
|
public Claim Build(DateTimeOffset? issuedAt = null)
|
||||||
{
|
{
|
||||||
if (_subject is null)
|
if (_subject is null)
|
||||||
throw new InvalidOperationException("Subject is required.");
|
throw new InvalidOperationException("Subject is required.");
|
||||||
@@ -409,7 +410,7 @@ public sealed class TrustLatticeEngine
|
|||||||
TrustLabel = _trustLabel,
|
TrustLabel = _trustLabel,
|
||||||
Assertions = _assertions,
|
Assertions = _assertions,
|
||||||
EvidenceRefs = _evidenceRefs,
|
EvidenceRefs = _evidenceRefs,
|
||||||
Time = new ClaimTimeInfo { IssuedAt = DateTimeOffset.UtcNow },
|
Time = new ClaimTimeInfo { IssuedAt = issuedAt ?? DateTimeOffset.UtcNow },
|
||||||
};
|
};
|
||||||
|
|
||||||
return _engine.IngestClaim(claim);
|
return _engine.IngestClaim(claim);
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
|||||||
/// <param name="detail">Optional detail text.</param>
|
/// <param name="detail">Optional detail text.</param>
|
||||||
/// <param name="principal">The principal making the assertion.</param>
|
/// <param name="principal">The principal making the assertion.</param>
|
||||||
/// <param name="trustLabel">Optional trust label.</param>
|
/// <param name="trustLabel">Optional trust label.</param>
|
||||||
|
/// <param name="issuedAt">Optional timestamp for deterministic testing.</param>
|
||||||
/// <returns>A normalized claim.</returns>
|
/// <returns>A normalized claim.</returns>
|
||||||
public Claim NormalizeStatement(
|
public Claim NormalizeStatement(
|
||||||
Subject subject,
|
Subject subject,
|
||||||
@@ -277,7 +278,8 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
|||||||
CycloneDxJustification justification = CycloneDxJustification.None,
|
CycloneDxJustification justification = CycloneDxJustification.None,
|
||||||
string? detail = null,
|
string? detail = null,
|
||||||
Principal? principal = null,
|
Principal? principal = null,
|
||||||
TrustLabel? trustLabel = null)
|
TrustLabel? trustLabel = null,
|
||||||
|
DateTimeOffset? issuedAt = null)
|
||||||
{
|
{
|
||||||
var assertions = new List<AtomAssertion>();
|
var assertions = new List<AtomAssertion>();
|
||||||
|
|
||||||
@@ -312,7 +314,7 @@ public sealed class CycloneDxVexNormalizer : IVexNormalizer
|
|||||||
Issuer = principal ?? Principal.Unknown,
|
Issuer = principal ?? Principal.Unknown,
|
||||||
Assertions = assertions,
|
Assertions = assertions,
|
||||||
TrustLabel = trustLabel,
|
TrustLabel = trustLabel,
|
||||||
Time = new ClaimTimeInfo { IssuedAt = DateTimeOffset.UtcNow },
|
Time = new ClaimTimeInfo { IssuedAt = issuedAt ?? DateTimeOffset.UtcNow },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user