< Summary

Information
Class: AsiBackbone.Core.Audit.DecisionReceiptLifecycleEvent
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Audit/DecisionReceiptLifecycleEvent.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 224
Line coverage: 100%
Branch coverage
91%
Covered branches: 31
Total branches: 34
Branch coverage: 91.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%22100%
get_StageSequence()100%11100%
get_HasDecisionReceiptId()100%11100%
get_HasMetadata()100%11100%
Create(...)100%22100%
FromDecisionReceipt(...)90%1010100%
NormalizeIdentifier(...)100%22100%
NormalizeOptional(...)100%22100%
NormalizeMetadata(...)87.5%1616100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Audit/DecisionReceiptLifecycleEvent.cs

#LineLine coverage
 1using System.Collections.ObjectModel;
 2
 3namespace AsiBackbone.Core.Audit;
 4
 5/// <summary>
 6/// Represents a framework-neutral lifecycle event linked to a governed decision receipt flow.
 7/// </summary>
 8/// <remarks>
 9/// Lifecycle events are append-only progress records. They allow acknowledgment, capability grant, gateway, outbox, and
 10/// </remarks>
 11public sealed class DecisionReceiptLifecycleEvent
 12{
 113    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 114        new ReadOnlyDictionary<string, string>(
 115            new Dictionary<string, string>(StringComparer.Ordinal));
 16
 10117    private DecisionReceiptLifecycleEvent(
 10118        string eventId,
 10119        DecisionReceiptLifecycleStage stage,
 10120        DateTimeOffset occurredUtc,
 10121        string correlationId,
 10122        string? decisionReceiptId,
 10123        string? traceId,
 10124        string? operationName,
 10125        string? outcome,
 10126        IReadOnlyDictionary<string, string> metadata)
 27    {
 10128        ArgumentException.ThrowIfNullOrWhiteSpace(eventId);
 10129        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 30
 9831        if (!Enum.IsDefined(stage))
 32        {
 133            throw new ArgumentOutOfRangeException(nameof(stage), stage, "Lifecycle stage must be a defined decision rece
 34        }
 35
 9736        EventId = eventId.Trim();
 9737        Stage = stage;
 9738        OccurredUtc = occurredUtc.ToUniversalTime();
 9739        CorrelationId = correlationId.Trim();
 9740        DecisionReceiptId = NormalizeOptional(decisionReceiptId);
 9741        TraceId = NormalizeOptional(traceId);
 9742        OperationName = NormalizeOptional(operationName);
 9743        Outcome = NormalizeOptional(outcome);
 9744        Metadata = metadata;
 9745    }
 46
 47    /// <summary>
 48    /// Gets the stable identifier for this lifecycle event.
 49    /// </summary>
 50    public string EventId { get; }
 51
 52    /// <summary>
 53    /// Gets the lifecycle stage represented by this event.
 54    /// </summary>
 55    public DecisionReceiptLifecycleStage Stage { get; }
 56
 57    /// <summary>
 58    /// Gets the stable sequence value for this lifecycle stage.
 59    /// </summary>
 5360    public int StageSequence => (int)Stage;
 61
 62    /// <summary>
 63    /// Gets the UTC timestamp when the lifecycle event occurred.
 64    /// </summary>
 65    public DateTimeOffset OccurredUtc { get; }
 66
 67    /// <summary>
 68    /// Gets the correlation identifier that links this lifecycle event to the original decision context.
 69    /// </summary>
 70    public string CorrelationId { get; }
 71
 72    /// <summary>
 73    /// Gets the related decision receipt identifier when the original decision receipt is available.
 74    /// </summary>
 75    public string? DecisionReceiptId { get; }
 76
 77    /// <summary>
 78    /// Gets the trace identifier associated with the lifecycle event, when supplied by the host or original residue.
 79    /// </summary>
 80    public string? TraceId { get; }
 81
 82    /// <summary>
 83    /// Gets the operation name associated with the lifecycle event, when supplied by the host or original residue.
 84    /// </summary>
 85    public string? OperationName { get; }
 86
 87    /// <summary>
 88    /// Gets the decision, gateway, emission, or host-defined outcome associated with this lifecycle event, when supplie
 89    /// </summary>
 90    public string? Outcome { get; }
 91
 92    /// <summary>
 93    /// Gets additional framework-neutral lifecycle metadata supplied by the host.
 94    /// </summary>
 95    public IReadOnlyDictionary<string, string> Metadata { get; }
 96
 97    /// <summary>
 98    /// Gets a value indicating whether this lifecycle event is linked to an decision receipt identifier.
 99    /// </summary>
 1100    public bool HasDecisionReceiptId => DecisionReceiptId is not null;
 101
 102    /// <summary>
 103    /// Gets a value indicating whether this lifecycle event contains metadata.
 104    /// </summary>
 1105    public bool HasMetadata => Metadata.Count > 0;
 106
 107    /// <summary>
 108    /// Creates an decision receipt lifecycle event.
 109    /// </summary>
 110    /// <param name="stage">The lifecycle stage represented by this event.</param>
 111    /// <param name="correlationId">The correlation identifier linking the event to the original decision context.</para
 112    /// <param name="decisionReceiptId">Optional decision receipt identifier when the original decision receipt is avail
 113    /// <param name="eventId">Optional lifecycle event identifier. When omitted, a new identifier is generated.</param>
 114    /// <param name="occurredUtc">Optional lifecycle timestamp. When omitted, the current UTC timestamp is used.</param>
 115    /// <param name="traceId">Optional trace identifier.</param>
 116    /// <param name="operationName">Optional operation name.</param>
 117    /// <param name="outcome">Optional lifecycle or host-defined outcome.</param>
 118    /// <param name="metadata">Optional host-provided lifecycle metadata.</param>
 119    /// <returns>An decision receipt lifecycle event.</returns>
 120    public static DecisionReceiptLifecycleEvent Create(
 121        DecisionReceiptLifecycleStage stage,
 122        string correlationId,
 123        string? decisionReceiptId = null,
 124        string? eventId = null,
 125        DateTimeOffset? occurredUtc = null,
 126        string? traceId = null,
 127        string? operationName = null,
 128        string? outcome = null,
 129        IReadOnlyDictionary<string, string>? metadata = null)
 130    {
 80131        return new DecisionReceiptLifecycleEvent(
 80132            NormalizeIdentifier(eventId),
 80133            stage,
 80134            occurredUtc ?? DateTimeOffset.UtcNow,
 80135            correlationId,
 80136            decisionReceiptId,
 80137            traceId,
 80138            operationName,
 80139            outcome,
 80140            NormalizeMetadata(metadata));
 141    }
 142
 143    /// <summary>
 144    /// Creates an decision receipt lifecycle event by copying correlation context from existing decision receipt.
 145    /// </summary>
 146    /// <param name="stage">The lifecycle stage represented by this event.</param>
 147    /// <param name="receipt">The original decision receipt to correlate with the lifecycle event.</param>
 148    /// <param name="correlationId">Optional correlation identifier override. When omitted, the receipt correlation iden
 149    /// <param name="decisionReceiptId">Optional decision receipt identifier override. When omitted, the receipt event i
 150    /// <param name="eventId">Optional lifecycle event identifier. When omitted, a new identifier is generated.</param>
 151    /// <param name="occurredUtc">Optional lifecycle timestamp. When omitted, the current UTC timestamp is used.</param>
 152    /// <param name="outcome">Optional lifecycle or host-defined outcome. When omitted, the receipt outcome is used.</pa
 153    /// <param name="metadata">Optional host-provided lifecycle metadata merged after receipt metadata.</param>
 154    /// <returns>An decision receipt lifecycle event.</returns>
 155    public static DecisionReceiptLifecycleEvent FromDecisionReceipt(
 156        DecisionReceiptLifecycleStage stage,
 157        IDecisionReceipt receipt,
 158        string? correlationId = null,
 159        string? decisionReceiptId = null,
 160        string? eventId = null,
 161        DateTimeOffset? occurredUtc = null,
 162        string? outcome = null,
 163        IReadOnlyDictionary<string, string>? metadata = null)
 164    {
 22165        ArgumentNullException.ThrowIfNull(receipt);
 166
 22167        string? effectiveCorrelationId = string.IsNullOrWhiteSpace(correlationId)
 22168            ? receipt.CorrelationId
 22169            : correlationId;
 170
 22171        return new DecisionReceiptLifecycleEvent(
 22172            NormalizeIdentifier(eventId),
 22173            stage,
 22174            occurredUtc ?? DateTimeOffset.UtcNow,
 22175            effectiveCorrelationId ?? throw new ArgumentException("A lifecycle event requires a correlation identifier f
 22176            string.IsNullOrWhiteSpace(decisionReceiptId) ? receipt.EventId : decisionReceiptId,
 22177            receipt.TraceId,
 22178            receipt.OperationName,
 22179            string.IsNullOrWhiteSpace(outcome) ? receipt.Outcome : outcome,
 22180            NormalizeMetadata(receipt.Metadata, metadata));
 181    }
 182
 183    private static string NormalizeIdentifier(string? identifier)
 184    {
 102185        return string.IsNullOrWhiteSpace(identifier)
 102186            ? Guid.NewGuid().ToString("N")
 102187            : identifier.Trim();
 188    }
 189
 190    private static string? NormalizeOptional(string? value)
 191    {
 388192        return string.IsNullOrWhiteSpace(value)
 388193            ? null
 388194            : value.Trim();
 195    }
 196
 197    private static IReadOnlyDictionary<string, string> NormalizeMetadata(
 198        params IReadOnlyDictionary<string, string>?[] metadataSets)
 199    {
 101200        Dictionary<string, string> normalizedMetadata = new(StringComparer.Ordinal);
 201
 446202        foreach (IReadOnlyDictionary<string, string>? metadata in metadataSets)
 203        {
 122204            if (metadata is null || metadata.Count == 0)
 205            {
 206                continue;
 207            }
 208
 648209            foreach (KeyValuePair<string, string> item in metadata)
 210            {
 258211                if (string.IsNullOrWhiteSpace(item.Key))
 212                {
 213                    continue;
 214                }
 215
 257216                normalizedMetadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 217            }
 218        }
 219
 101220        return normalizedMetadata.Count == 0
 101221            ? EmptyMetadata
 101222            : new ReadOnlyDictionary<string, string>(normalizedMetadata);
 223    }
 224}