< Summary

Information
Class: AsiBackbone.Core.Audit.AuditResidueLifecycleEvent
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Audit/AuditResidueLifecycleEvent.cs
Line coverage
100%
Covered lines: 78
Uncovered lines: 0
Coverable lines: 78
Total lines: 224
Line coverage: 100%
Branch coverage
88%
Covered branches: 30
Total branches: 34
Branch coverage: 88.2%
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_EventId()100%11100%
get_Stage()100%11100%
get_StageSequence()100%11100%
get_OccurredUtc()100%11100%
get_CorrelationId()100%11100%
get_AuditResidueId()100%11100%
get_TraceId()100%11100%
get_OperationName()100%11100%
get_Outcome()100%11100%
get_Metadata()100%11100%
get_HasAuditResidueId()100%11100%
get_HasMetadata()100%11100%
Create(...)100%22100%
FromResidue(...)80%1010100%
NormalizeIdentifier(...)100%22100%
NormalizeOptional(...)100%22100%
NormalizeMetadata(...)87.5%1616100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Audit/AuditResidueLifecycleEvent.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 audit residue flow.
 7/// </summary>
 8/// <remarks>
 9/// Lifecycle events are append-only progress records. They allow acknowledgment, capability token, gateway, outbox, and
 10/// </remarks>
 11public sealed class AuditResidueLifecycleEvent
 12{
 113    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 114        new ReadOnlyDictionary<string, string>(
 115            new Dictionary<string, string>(StringComparer.Ordinal));
 16
 10317    private AuditResidueLifecycleEvent(
 10318        string eventId,
 10319        AuditResidueLifecycleStage stage,
 10320        DateTimeOffset occurredUtc,
 10321        string correlationId,
 10322        string? auditResidueId,
 10323        string? traceId,
 10324        string? operationName,
 10325        string? outcome,
 10326        IReadOnlyDictionary<string, string> metadata)
 27    {
 10328        ArgumentException.ThrowIfNullOrWhiteSpace(eventId);
 10329        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 30
 10031        if (!Enum.IsDefined(stage))
 32        {
 133            throw new ArgumentOutOfRangeException(nameof(stage), stage, "Lifecycle stage must be a defined audit residue
 34        }
 35
 9936        EventId = eventId.Trim();
 9937        Stage = stage;
 9938        OccurredUtc = occurredUtc.ToUniversalTime();
 9939        CorrelationId = correlationId.Trim();
 9940        AuditResidueId = NormalizeOptional(auditResidueId);
 9941        TraceId = NormalizeOptional(traceId);
 9942        OperationName = NormalizeOptional(operationName);
 9943        Outcome = NormalizeOptional(outcome);
 9944        Metadata = metadata;
 9945    }
 46
 47    /// <summary>
 48    /// Gets the stable identifier for this lifecycle event.
 49    /// </summary>
 13950    public string EventId { get; }
 51
 52    /// <summary>
 53    /// Gets the lifecycle stage represented by this event.
 54    /// </summary>
 13455    public AuditResidueLifecycleStage Stage { get; }
 56
 57    /// <summary>
 58    /// Gets the stable sequence value for this lifecycle stage.
 59    /// </summary>
 5260    public int StageSequence => (int)Stage;
 61
 62    /// <summary>
 63    /// Gets the UTC timestamp when the lifecycle event occurred.
 64    /// </summary>
 8465    public DateTimeOffset OccurredUtc { get; }
 66
 67    /// <summary>
 68    /// Gets the correlation identifier that links this lifecycle event to the original decision context.
 69    /// </summary>
 9070    public string CorrelationId { get; }
 71
 72    /// <summary>
 73    /// Gets the related audit residue identifier when the original decision residue is available.
 74    /// </summary>
 5975    public string? AuditResidueId { get; }
 76
 77    /// <summary>
 78    /// Gets the trace identifier associated with the lifecycle event, when supplied by the host or original residue.
 79    /// </summary>
 4780    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>
 4585    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>
 4590    public string? Outcome { get; }
 91
 92    /// <summary>
 93    /// Gets additional framework-neutral lifecycle metadata supplied by the host.
 94    /// </summary>
 4995    public IReadOnlyDictionary<string, string> Metadata { get; }
 96
 97    /// <summary>
 98    /// Gets a value indicating whether this lifecycle event is linked to an audit residue identifier.
 99    /// </summary>
 1100    public bool HasAuditResidueId => AuditResidueId 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 audit residue 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="auditResidueId">Optional audit residue identifier when the original decision residue is available.<
 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 audit residue lifecycle event.</returns>
 120    public static AuditResidueLifecycleEvent Create(
 121        AuditResidueLifecycleStage stage,
 122        string correlationId,
 123        string? auditResidueId = 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    {
 101131        return new AuditResidueLifecycleEvent(
 101132            NormalizeIdentifier(eventId),
 101133            stage,
 101134            occurredUtc ?? DateTimeOffset.UtcNow,
 101135            correlationId,
 101136            auditResidueId,
 101137            traceId,
 101138            operationName,
 101139            outcome,
 101140            NormalizeMetadata(metadata));
 141    }
 142
 143    /// <summary>
 144    /// Creates an audit residue lifecycle event by copying correlation context from existing audit residue.
 145    /// </summary>
 146    /// <param name="stage">The lifecycle stage represented by this event.</param>
 147    /// <param name="residue">The original audit residue to correlate with the lifecycle event.</param>
 148    /// <param name="correlationId">Optional correlation identifier override. When omitted, the residue correlation iden
 149    /// <param name="auditResidueId">Optional audit residue identifier override. When omitted, the residue event identif
 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 residue outcome is used.</pa
 153    /// <param name="metadata">Optional host-provided lifecycle metadata merged after residue metadata.</param>
 154    /// <returns>An audit residue lifecycle event.</returns>
 155    public static AuditResidueLifecycleEvent FromResidue(
 156        AuditResidueLifecycleStage stage,
 157        IAsiBackboneAuditResidue residue,
 158        string? correlationId = null,
 159        string? auditResidueId = null,
 160        string? eventId = null,
 161        DateTimeOffset? occurredUtc = null,
 162        string? outcome = null,
 163        IReadOnlyDictionary<string, string>? metadata = null)
 164    {
 3165        ArgumentNullException.ThrowIfNull(residue);
 166
 3167        string? effectiveCorrelationId = string.IsNullOrWhiteSpace(correlationId)
 3168            ? residue.CorrelationId
 3169            : correlationId;
 170
 3171        return new AuditResidueLifecycleEvent(
 3172            NormalizeIdentifier(eventId),
 3173            stage,
 3174            occurredUtc ?? DateTimeOffset.UtcNow,
 3175            effectiveCorrelationId ?? throw new ArgumentException("A lifecycle event requires a correlation identifier f
 3176            string.IsNullOrWhiteSpace(auditResidueId) ? residue.EventId : auditResidueId,
 3177            residue.TraceId,
 3178            residue.OperationName,
 3179            string.IsNullOrWhiteSpace(outcome) ? residue.Outcome : outcome,
 3180            NormalizeMetadata(residue.Metadata, metadata));
 181    }
 182
 183    private static string NormalizeIdentifier(string? identifier)
 184    {
 104185        return string.IsNullOrWhiteSpace(identifier)
 104186            ? Guid.NewGuid().ToString("N")
 104187            : identifier.Trim();
 188    }
 189
 190    private static string? NormalizeOptional(string? value)
 191    {
 396192        return string.IsNullOrWhiteSpace(value)
 396193            ? null
 396194            : value.Trim();
 195    }
 196
 197    private static IReadOnlyDictionary<string, string> NormalizeMetadata(
 198        params IReadOnlyDictionary<string, string>?[] metadataSets)
 199    {
 103200        Dictionary<string, string> normalizedMetadata = new(StringComparer.Ordinal);
 201
 416202        foreach (IReadOnlyDictionary<string, string>? metadata in metadataSets)
 203        {
 105204            if (metadata is null || metadata.Count == 0)
 205            {
 206                continue;
 207            }
 208
 484209            foreach (KeyValuePair<string, string> item in metadata)
 210            {
 155211                if (string.IsNullOrWhiteSpace(item.Key))
 212                {
 213                    continue;
 214                }
 215
 154216                normalizedMetadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 217            }
 218        }
 219
 103220        return normalizedMetadata.Count == 0
 103221            ? EmptyMetadata
 103222            : new ReadOnlyDictionary<string, string>(normalizedMetadata);
 223    }
 224}