using StellaOps.Policy.Engine.Domain; using System.Collections.Concurrent; namespace StellaOps.Policy.Engine.Services; internal sealed class InMemoryPolicyPackRepository : IPolicyPackRepository { private readonly ConcurrentDictionary packs = new(StringComparer.OrdinalIgnoreCase); private readonly TimeProvider _timeProvider; public InMemoryPolicyPackRepository(TimeProvider timeProvider) { _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); } public Task CreateAsync(string packId, string? displayName, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(packId); var created = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, displayName, _timeProvider.GetUtcNow())); return Task.FromResult(created); } public Task> ListAsync(CancellationToken cancellationToken) { IReadOnlyList list = packs.Values .OrderBy(pack => pack.PackId, StringComparer.Ordinal) .ToList(); return Task.FromResult(list); } public Task UpsertRevisionAsync(string packId, int version, bool requiresTwoPersonApproval, PolicyRevisionStatus initialStatus, CancellationToken cancellationToken) { var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, _timeProvider.GetUtcNow())); int revisionVersion = version > 0 ? version : pack.GetNextVersion(); var revision = pack.GetOrAddRevision( revisionVersion, v => new PolicyRevisionRecord(v, requiresTwoPersonApproval, initialStatus, _timeProvider.GetUtcNow())); if (revision.Status != initialStatus) { revision.SetStatus(initialStatus, _timeProvider.GetUtcNow()); } return Task.FromResult(revision); } public Task GetRevisionAsync(string packId, int version, CancellationToken cancellationToken) { if (!packs.TryGetValue(packId, out var pack)) { return Task.FromResult(null); } return Task.FromResult(pack.TryGetRevision(version, out var revision) ? revision : null); } public Task RecordActivationAsync(string packId, int version, string actorId, DateTimeOffset timestamp, string? comment, CancellationToken cancellationToken) { if (!packs.TryGetValue(packId, out var pack)) { return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.PackNotFound, null)); } if (!pack.TryGetRevision(version, out var revision)) { return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.RevisionNotFound, null)); } if (revision.Status == PolicyRevisionStatus.Active) { return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.AlreadyActive, revision)); } if (revision.Status != PolicyRevisionStatus.Approved) { return Task.FromResult(new PolicyActivationResult(PolicyActivationResultStatus.NotApproved, revision)); } var approvalStatus = revision.AddApproval(new PolicyActivationApproval(actorId, timestamp, comment)); return Task.FromResult(approvalStatus switch { PolicyActivationApprovalStatus.Duplicate => new PolicyActivationResult(PolicyActivationResultStatus.DuplicateApproval, revision), PolicyActivationApprovalStatus.Pending when revision.RequiresTwoPersonApproval => new PolicyActivationResult(PolicyActivationResultStatus.PendingSecondApproval, revision), PolicyActivationApprovalStatus.Pending => ActivateRevision(revision, timestamp), PolicyActivationApprovalStatus.ThresholdReached => ActivateRevision(revision, timestamp), _ => throw new InvalidOperationException("Unknown activation approval status.") }); } private static PolicyActivationResult ActivateRevision(PolicyRevisionRecord revision, DateTimeOffset timestamp) { revision.SetStatus(PolicyRevisionStatus.Active, timestamp); return new PolicyActivationResult(PolicyActivationResultStatus.Activated, revision); } public Task StoreBundleAsync(string packId, int version, PolicyBundleRecord bundle, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(bundle); var pack = packs.GetOrAdd(packId, id => new PolicyPackRecord(id, null, _timeProvider.GetUtcNow())); var revision = pack.GetOrAddRevision(version > 0 ? version : pack.GetNextVersion(), v => new PolicyRevisionRecord(v, requiresTwoPerson: false, status: PolicyRevisionStatus.Draft, _timeProvider.GetUtcNow())); revision.SetBundle(bundle); return Task.FromResult(bundle); } public Task GetBundleAsync(string packId, int version, CancellationToken cancellationToken) { if (!packs.TryGetValue(packId, out var pack)) { return Task.FromResult(null); } if (!pack.TryGetRevision(version, out var revision)) { return Task.FromResult(null); } return Task.FromResult(revision.Bundle); } }