up
Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using StellaOps.Policy.Engine.Storage.Mongo.Documents;
|
||||
using StellaOps.Policy.Engine.Storage.Mongo.Repositories;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Storage.InMemory;
|
||||
|
||||
@@ -12,340 +12,178 @@ namespace StellaOps.Policy.Engine.Storage.InMemory;
|
||||
/// </summary>
|
||||
public sealed class InMemoryExceptionRepository : IExceptionRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<(string Tenant, string Id), PolicyExceptionDocument> _exceptions = new();
|
||||
private readonly ConcurrentDictionary<(string Tenant, string Id), ExceptionBindingDocument> _bindings = new();
|
||||
private readonly ConcurrentDictionary<(string Tenant, Guid Id), ExceptionEntity> _exceptions = new();
|
||||
|
||||
public Task<PolicyExceptionDocument> CreateExceptionAsync(PolicyExceptionDocument exception, CancellationToken cancellationToken)
|
||||
public Task<ExceptionEntity> CreateAsync(ExceptionEntity exception, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_exceptions[(exception.TenantId.ToLowerInvariant(), exception.Id)] = Clone(exception);
|
||||
return Task.FromResult(exception);
|
||||
var id = exception.Id == Guid.Empty ? Guid.NewGuid() : exception.Id;
|
||||
var stored = Copy(exception, id);
|
||||
_exceptions[(Normalize(exception.TenantId), id)] = stored;
|
||||
return Task.FromResult(stored);
|
||||
}
|
||||
|
||||
public Task<PolicyExceptionDocument?> GetExceptionAsync(string tenantId, string exceptionId, CancellationToken cancellationToken)
|
||||
public Task<ExceptionEntity?> GetByIdAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_exceptions.TryGetValue((tenantId.ToLowerInvariant(), exceptionId), out var value);
|
||||
return Task.FromResult(value is null ? null : Clone(value));
|
||||
_exceptions.TryGetValue((Normalize(tenantId), id), out var entity);
|
||||
return Task.FromResult(entity is null ? null : Copy(entity));
|
||||
}
|
||||
|
||||
public Task<PolicyExceptionDocument?> UpdateExceptionAsync(PolicyExceptionDocument exception, CancellationToken cancellationToken)
|
||||
public Task<ExceptionEntity?> GetByNameAsync(string tenantId, string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_exceptions[(exception.TenantId.ToLowerInvariant(), exception.Id)] = Clone(exception);
|
||||
return Task.FromResult<PolicyExceptionDocument?>(exception);
|
||||
var match = _exceptions
|
||||
.Where(kvp => kvp.Key.Tenant == Normalize(tenantId) && kvp.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(kvp => Copy(kvp.Value))
|
||||
.FirstOrDefault();
|
||||
|
||||
return Task.FromResult(match);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> ListExceptionsAsync(ExceptionQueryOptions options, CancellationToken cancellationToken)
|
||||
public Task<IReadOnlyList<ExceptionEntity>> GetAllAsync(
|
||||
string tenantId,
|
||||
ExceptionStatus? status = null,
|
||||
int limit = 100,
|
||||
int offset = 0,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _exceptions.Values.AsEnumerable();
|
||||
var query = _exceptions
|
||||
.Where(kvp => kvp.Key.Tenant == Normalize(tenantId))
|
||||
.Select(kvp => kvp.Value);
|
||||
|
||||
if (options.Statuses.Any())
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(e => options.Statuses.Contains(e.Status, StringComparer.OrdinalIgnoreCase));
|
||||
query = query.Where(e => e.Status == status.Value);
|
||||
}
|
||||
|
||||
if (options.Types.Any())
|
||||
{
|
||||
query = query.Where(e => options.Types.Contains(e.ExceptionType, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
var results = query
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.Select(x => Copy(x))
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(query.Select(Clone).ToImmutableArray());
|
||||
return Task.FromResult<IReadOnlyList<ExceptionEntity>>(results);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> ListExceptionsAsync(string tenantId, ExceptionQueryOptions options, CancellationToken cancellationToken)
|
||||
public Task<IReadOnlyList<ExceptionEntity>> GetActiveForProjectAsync(
|
||||
string tenantId,
|
||||
string projectId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var scoped = _exceptions.Values.Where(e => e.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var result = scoped.AsEnumerable();
|
||||
var results = _exceptions
|
||||
.Where(kvp => kvp.Key.Tenant == Normalize(tenantId))
|
||||
.Select(kvp => kvp.Value)
|
||||
.Where(e => e.Status == ExceptionStatus.Active)
|
||||
.Where(e => string.IsNullOrWhiteSpace(e.ProjectId) || string.Equals(e.ProjectId, projectId, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(x => Copy(x))
|
||||
.ToList();
|
||||
|
||||
if (options.Statuses.Any())
|
||||
{
|
||||
result = result.Where(e => options.Statuses.Contains(e.Status, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (options.Types.Any())
|
||||
{
|
||||
result = result.Where(e => options.Types.Contains(e.ExceptionType, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return Task.FromResult(result.Select(Clone).ToImmutableArray());
|
||||
return Task.FromResult<IReadOnlyList<ExceptionEntity>>(results);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> FindApplicableExceptionsAsync(string tenantId, ExceptionQueryOptions options, CancellationToken cancellationToken)
|
||||
public Task<IReadOnlyList<ExceptionEntity>> GetActiveForRuleAsync(
|
||||
string tenantId,
|
||||
string ruleName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _exceptions.Values
|
||||
.Where(e => e.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.Status.Equals("active", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
var results = _exceptions
|
||||
.Where(kvp => kvp.Key.Tenant == Normalize(tenantId))
|
||||
.Select(kvp => kvp.Value)
|
||||
.Where(e => e.Status == ExceptionStatus.Active)
|
||||
.Where(e =>
|
||||
string.IsNullOrWhiteSpace(e.RulePattern) ||
|
||||
Regex.IsMatch(ruleName, e.RulePattern, RegexOptions.IgnoreCase))
|
||||
.Select(x => Copy(x))
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(results);
|
||||
return Task.FromResult<IReadOnlyList<ExceptionEntity>>(results);
|
||||
}
|
||||
|
||||
public Task<bool> UpdateExceptionStatusAsync(string tenantId, string exceptionId, string newStatus, DateTimeOffset timestamp, CancellationToken cancellationToken)
|
||||
public Task<bool> UpdateAsync(ExceptionEntity exception, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = (tenantId.ToLowerInvariant(), exceptionId);
|
||||
if (!_exceptions.TryGetValue(key, out var existing))
|
||||
var key = (Normalize(exception.TenantId), exception.Id);
|
||||
if (!_exceptions.ContainsKey(key))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var updated = Clone(existing);
|
||||
updated.Status = newStatus;
|
||||
updated.UpdatedAt = timestamp;
|
||||
if (newStatus == "active")
|
||||
{
|
||||
updated.ActivatedAt = timestamp;
|
||||
}
|
||||
if (newStatus == "expired")
|
||||
{
|
||||
updated.RevokedAt = timestamp;
|
||||
}
|
||||
|
||||
_exceptions[key] = updated;
|
||||
_exceptions[key] = Copy(exception);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task<bool> RevokeExceptionAsync(string tenantId, string exceptionId, string revokedBy, string? reason, DateTimeOffset timestamp, CancellationToken cancellationToken)
|
||||
public Task<bool> ApproveAsync(string tenantId, Guid id, string approvedBy, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return UpdateExceptionStatusAsync(tenantId, exceptionId, "revoked", timestamp, cancellationToken);
|
||||
// In-memory implementation treats approve as no-op since status enum has no pending state.
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> GetExpiringExceptionsAsync(string tenantId, DateTimeOffset from, DateTimeOffset to, CancellationToken cancellationToken)
|
||||
public Task<bool> RevokeAsync(string tenantId, Guid id, string revokedBy, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _exceptions.Values
|
||||
.Where(e => e.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.Status.Equals("active", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.ExpiresAt is not null && e.ExpiresAt >= from && e.ExpiresAt <= to)
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> GetPendingActivationsAsync(string tenantId, DateTimeOffset asOf, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _exceptions.Values
|
||||
.Where(e => e.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.Status.Equals("approved", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.EffectiveFrom is null || e.EffectiveFrom <= asOf)
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<ExceptionReviewDocument> CreateReviewAsync(ExceptionReviewDocument review, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(review);
|
||||
}
|
||||
|
||||
public Task<ExceptionReviewDocument?> GetReviewAsync(string tenantId, string reviewId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<ExceptionReviewDocument?>(null);
|
||||
}
|
||||
|
||||
public Task<ExceptionReviewDocument?> AddReviewDecisionAsync(string tenantId, string reviewId, ReviewDecisionDocument decision, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<ExceptionReviewDocument?>(null);
|
||||
}
|
||||
|
||||
public Task<ExceptionReviewDocument?> CompleteReviewAsync(string tenantId, string reviewId, string finalStatus, DateTimeOffset completedAt, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<ExceptionReviewDocument?>(null);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<ExceptionReviewDocument>> GetReviewsForExceptionAsync(string tenantId, string exceptionId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(ImmutableArray<ExceptionReviewDocument>.Empty);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<ExceptionReviewDocument>> GetPendingReviewsAsync(string tenantId, string? reviewerId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(ImmutableArray<ExceptionReviewDocument>.Empty);
|
||||
}
|
||||
|
||||
public Task<ExceptionBindingDocument> UpsertBindingAsync(ExceptionBindingDocument binding, CancellationToken cancellationToken)
|
||||
{
|
||||
_bindings[(binding.TenantId.ToLowerInvariant(), binding.Id)] = Clone(binding);
|
||||
return Task.FromResult(binding);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<ExceptionBindingDocument>> GetBindingsForExceptionAsync(string tenantId, string exceptionId, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _bindings.Values
|
||||
.Where(b => b.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase) && b.ExceptionId == exceptionId)
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<ExceptionBindingDocument>> GetActiveBindingsForAssetAsync(string tenantId, string assetId, DateTimeOffset asOf, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _bindings.Values
|
||||
.Where(b => b.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(b => b.AssetId == assetId)
|
||||
.Where(b => b.Status == "active")
|
||||
.Where(b => b.EffectiveFrom <= asOf && (b.ExpiresAt is null || b.ExpiresAt > asOf))
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<long> DeleteBindingsForExceptionAsync(string tenantId, string exceptionId, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var removed = _bindings.Where(kvp => kvp.Key.Tenant == tenant && kvp.Value.ExceptionId == exceptionId).ToList();
|
||||
foreach (var kvp in removed)
|
||||
var key = (Normalize(tenantId), id);
|
||||
if (_exceptions.TryGetValue(key, out var existing))
|
||||
{
|
||||
_bindings.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
|
||||
return Task.FromResult((long)removed.Count);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<ExceptionBindingDocument>> GetExpiredBindingsAsync(string tenantId, DateTimeOffset asOf, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _bindings.Values
|
||||
.Where(b => b.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(b => b.Status == "active")
|
||||
.Where(b => b.ExpiresAt is not null && b.ExpiresAt < asOf)
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyDictionary<string, int>> GetExceptionCountsByStatusAsync(string tenantId, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var counts = _exceptions.Values
|
||||
.Where(e => e.TenantId.Equals(tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.GroupBy(e => e.Status)
|
||||
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return Task.FromResult((IReadOnlyDictionary<string, int>)counts);
|
||||
}
|
||||
|
||||
|
||||
public Task<ImmutableArray<ExceptionBindingDocument>> GetExpiredBindingsAsync(string tenantId, DateTimeOffset asOf, int limit, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var results = _bindings.Values
|
||||
.Where(b => string.Equals(b.TenantId, tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(b => b.Status == "active")
|
||||
.Where(b => b.ExpiresAt is not null && b.ExpiresAt < asOf)
|
||||
.Take(limit)
|
||||
.Select(Clone)
|
||||
.ToImmutableArray();
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<bool> UpdateBindingStatusAsync(string tenantId, string bindingId, string newStatus, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = _bindings.Keys.FirstOrDefault(k => string.Equals(k.Tenant, tenantId, StringComparison.OrdinalIgnoreCase) && k.Id == bindingId);
|
||||
if (key == default)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
if (_bindings.TryGetValue(key, out var binding))
|
||||
{
|
||||
var updated = Clone(binding);
|
||||
updated.Status = newStatus;
|
||||
_bindings[key] = updated;
|
||||
_exceptions[key] = Copy(
|
||||
existing,
|
||||
statusOverride: ExceptionStatus.Revoked,
|
||||
revokedAtOverride: DateTimeOffset.UtcNow,
|
||||
revokedByOverride: revokedBy);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<PolicyExceptionDocument>> FindApplicableExceptionsAsync(string tenantId, string assetId, string? advisoryId, DateTimeOffset asOf, CancellationToken cancellationToken)
|
||||
public Task<int> ExpireAsync(string tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenant = tenantId.ToLowerInvariant();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var normalizedTenant = Normalize(tenantId);
|
||||
var expired = 0;
|
||||
|
||||
var activeExceptions = _exceptions.Values
|
||||
.Where(e => string.Equals(e.TenantId, tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => e.Status.Equals("active", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(e => (e.EffectiveFrom is null || e.EffectiveFrom <= asOf) && (e.ExpiresAt is null || e.ExpiresAt > asOf))
|
||||
.ToDictionary(e => e.Id, Clone);
|
||||
|
||||
if (activeExceptions.Count == 0)
|
||||
foreach (var kvp in _exceptions.Where(k => k.Key.Tenant == normalizedTenant))
|
||||
{
|
||||
return Task.FromResult(ImmutableArray<PolicyExceptionDocument>.Empty);
|
||||
}
|
||||
|
||||
var matchingIds = _bindings.Values
|
||||
.Where(b => string.Equals(b.TenantId, tenant, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(b => b.Status == "active")
|
||||
.Where(b => b.EffectiveFrom <= asOf && (b.ExpiresAt is null || b.ExpiresAt > asOf))
|
||||
.Where(b => b.AssetId == assetId)
|
||||
.Where(b => advisoryId is null || string.IsNullOrEmpty(b.AdvisoryId) || b.AdvisoryId == advisoryId)
|
||||
.Select(b => b.ExceptionId)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var ex in activeExceptions.Values)
|
||||
{
|
||||
if (ex.Scope.ApplyToAll)
|
||||
if (kvp.Value.Status == ExceptionStatus.Active && kvp.Value.ExpiresAt is not null && kvp.Value.ExpiresAt <= now)
|
||||
{
|
||||
matchingIds.Add(ex.Id);
|
||||
}
|
||||
else if (ex.Scope.AssetIds.Contains(assetId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
matchingIds.Add(ex.Id);
|
||||
}
|
||||
else if (advisoryId is not null && ex.Scope.AdvisoryIds.Contains(advisoryId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
matchingIds.Add(ex.Id);
|
||||
_exceptions[kvp.Key] = Copy(
|
||||
kvp.Value,
|
||||
statusOverride: ExceptionStatus.Expired,
|
||||
revokedAtOverride: now);
|
||||
expired++;
|
||||
}
|
||||
}
|
||||
|
||||
var result = matchingIds
|
||||
.Where(activeExceptions.ContainsKey)
|
||||
.Select(id => activeExceptions[id])
|
||||
.ToImmutableArray();
|
||||
|
||||
return Task.FromResult(result);
|
||||
return Task.FromResult(expired);
|
||||
}
|
||||
|
||||
private static PolicyExceptionDocument Clone(PolicyExceptionDocument source)
|
||||
public Task<bool> DeleteAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new PolicyExceptionDocument
|
||||
{
|
||||
Id = source.Id,
|
||||
TenantId = source.TenantId,
|
||||
Name = source.Name,
|
||||
ExceptionType = source.ExceptionType,
|
||||
Status = source.Status,
|
||||
EffectiveFrom = source.EffectiveFrom,
|
||||
ExpiresAt = source.ExpiresAt,
|
||||
CreatedAt = source.CreatedAt,
|
||||
UpdatedAt = source.UpdatedAt,
|
||||
ActivatedAt = source.ActivatedAt,
|
||||
RevokedAt = source.RevokedAt,
|
||||
RevokedBy = source.RevokedBy,
|
||||
RevocationReason = source.RevocationReason,
|
||||
Scope = source.Scope,
|
||||
RiskAssessment = source.RiskAssessment,
|
||||
Tags = source.Tags,
|
||||
};
|
||||
var key = (Normalize(tenantId), id);
|
||||
return Task.FromResult(_exceptions.TryRemove(key, out _));
|
||||
}
|
||||
|
||||
private static ExceptionBindingDocument Clone(ExceptionBindingDocument source)
|
||||
private static string Normalize(string value) => value.ToLowerInvariant();
|
||||
|
||||
private static ExceptionEntity Copy(
|
||||
ExceptionEntity source,
|
||||
Guid? idOverride = null,
|
||||
ExceptionStatus? statusOverride = null,
|
||||
DateTimeOffset? revokedAtOverride = null,
|
||||
string? revokedByOverride = null) => new()
|
||||
{
|
||||
return new ExceptionBindingDocument
|
||||
{
|
||||
Id = source.Id,
|
||||
TenantId = source.TenantId,
|
||||
ExceptionId = source.ExceptionId,
|
||||
AssetId = source.AssetId,
|
||||
AdvisoryId = source.AdvisoryId,
|
||||
Status = source.Status,
|
||||
EffectiveFrom = source.EffectiveFrom,
|
||||
ExpiresAt = source.ExpiresAt,
|
||||
};
|
||||
}
|
||||
Id = idOverride ?? source.Id,
|
||||
TenantId = source.TenantId,
|
||||
Name = source.Name,
|
||||
Description = source.Description,
|
||||
RulePattern = source.RulePattern,
|
||||
ResourcePattern = source.ResourcePattern,
|
||||
ArtifactPattern = source.ArtifactPattern,
|
||||
ProjectId = source.ProjectId,
|
||||
Reason = source.Reason,
|
||||
Status = statusOverride ?? source.Status,
|
||||
ExpiresAt = source.ExpiresAt,
|
||||
ApprovedBy = source.ApprovedBy,
|
||||
ApprovedAt = source.ApprovedAt,
|
||||
RevokedBy = revokedByOverride ?? source.RevokedBy,
|
||||
RevokedAt = revokedAtOverride ?? source.RevokedAt,
|
||||
Metadata = source.Metadata,
|
||||
CreatedAt = source.CreatedAt,
|
||||
CreatedBy = source.CreatedBy
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user