< Summary

Information
Class: AsiBackbone.Core.Audit.DecisionReceipt
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Audit/DecisionReceipt.cs
Line coverage
100%
Covered lines: 257
Uncovered lines: 0
Coverable lines: 257
Total lines: 674
Line coverage: 100%
Branch coverage
100%
Covered branches: 64
Total branches: 64
Branch coverage: 100%
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%11100%
get_HasReasonCodes()100%11100%
get_HasMetadata()100%11100%
Create(...)100%11100%
FromDecision(...)100%11100%
FromConstraint(...)100%11100%
CreateCore(...)100%44100%
NormalizeIdentifier(...)100%22100%
NormalizeDecisionReceiptId(...)100%22100%
NormalizeOptional(...)100%22100%
NormalizeNonNegative(...)100%22100%
NormalizeNonNegative(...)100%22100%
NormalizeRiskScore(...)100%88100%
NormalizeReasonCodes(...)100%1818100%
UseTrustedReasonCodes(...)100%22100%
AddNormalizedReasonCode(...)100%22100%
CreateReasonCodeCollection(...)100%44100%
NormalizeMetadata(...)100%1616100%

File(s)

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

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using AsiBackbone.Core.Actors;
 3using AsiBackbone.Core.Constraints;
 4using AsiBackbone.Core.Decisions;
 5using AsiBackbone.Core.Serialization;
 6using AsiBackbone.Core.Signing;
 7
 8namespace AsiBackbone.Core.Audit;
 9
 10/// <summary>
 11/// Represents the framework-neutral decision receipt produced by an AsiBackbone operation.
 12/// </summary>
 13public sealed class DecisionReceipt : IDecisionReceipt
 14{
 515    private static readonly ReadOnlyCollection<string> EmptyReasonCodes =
 516        Array.AsReadOnly(Array.Empty<string>());
 17
 518    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 519        new ReadOnlyDictionary<string, string>(
 520            new Dictionary<string, string>(StringComparer.Ordinal));
 21
 17622    private DecisionReceipt(
 17623        string eventId,
 17624        string decisionReceiptId,
 17625        string schemaVersion,
 17626        DateTimeOffset occurredUtc,
 17627        string actorId,
 17628        GovernanceActorType actorType,
 17629        string? actorDisplayName,
 17630        string operationName,
 17631        string outcome,
 17632        IReadOnlyList<string> reasonCodes,
 17633        string? correlationId,
 17634        string? traceId,
 17635        string? spanId,
 17636        string? parentSpanId,
 17637        long? decisionLatencyMs,
 17638        string? constraintSetHash,
 17639        int? constraintCount,
 17640        double? riskScore,
 17641        string? policyScope,
 17642        string? tenantHash,
 17643        string? organizationHash,
 17644        string? emitterStatus,
 17645        string? emitterProvider,
 17646        long? outboxSequence,
 17647        string? gatewayExecutionId,
 17648        string? decisionStage,
 17649        string? policyVersion,
 17650        string? policyHash,
 17651        IReadOnlyDictionary<string, string> metadata)
 52    {
 17653        ArgumentException.ThrowIfNullOrWhiteSpace(eventId);
 17654        ArgumentException.ThrowIfNullOrWhiteSpace(decisionReceiptId);
 17655        ArgumentException.ThrowIfNullOrWhiteSpace(actorId);
 17656        ArgumentException.ThrowIfNullOrWhiteSpace(operationName);
 17357        ArgumentException.ThrowIfNullOrWhiteSpace(outcome);
 58
 17059        EventId = eventId.Trim();
 17060        DecisionReceiptId = decisionReceiptId.Trim();
 17061        SchemaVersion = GovernanceSchemaVersions.Normalize(schemaVersion);
 17062        OccurredUtc = occurredUtc.ToUniversalTime();
 17063        ActorId = actorId.Trim();
 17064        ActorType = actorType;
 17065        ActorDisplayName = NormalizeOptional(actorDisplayName);
 17066        OperationName = operationName.Trim();
 17067        Outcome = outcome.Trim();
 17068        ReasonCodes = reasonCodes;
 17069        CorrelationId = NormalizeOptional(correlationId);
 17070        TraceId = NormalizeOptional(traceId);
 17071        SpanId = NormalizeOptional(spanId);
 17072        ParentSpanId = NormalizeOptional(parentSpanId);
 17073        DecisionLatencyMs = NormalizeNonNegative(decisionLatencyMs, nameof(decisionLatencyMs));
 16774        ConstraintSetHash = NormalizeOptional(constraintSetHash);
 16775        ConstraintCount = NormalizeNonNegative(constraintCount, nameof(constraintCount));
 16476        RiskScore = NormalizeRiskScore(riskScore);
 15777        PolicyScope = NormalizeOptional(policyScope);
 15778        TenantHash = NormalizeOptional(tenantHash);
 15779        OrganizationHash = NormalizeOptional(organizationHash);
 15780        EmitterStatus = NormalizeOptional(emitterStatus);
 15781        EmitterProvider = NormalizeOptional(emitterProvider);
 15782        OutboxSequence = NormalizeNonNegative(outboxSequence, nameof(outboxSequence));
 15483        GatewayExecutionId = NormalizeOptional(gatewayExecutionId);
 15484        DecisionStage = NormalizeOptional(decisionStage);
 15485        PolicyVersion = NormalizeOptional(policyVersion);
 15486        PolicyHash = NormalizeOptional(policyHash);
 15487        Metadata = metadata;
 15488    }
 89
 90    /// <inheritdoc />
 91    public string EventId { get; }
 92
 93    /// <inheritdoc />
 94    public string DecisionReceiptId { get; }
 95
 96    /// <inheritdoc />
 97    public string SchemaVersion { get; }
 98
 99    /// <inheritdoc />
 100    public DateTimeOffset OccurredUtc { get; }
 101
 102    /// <inheritdoc />
 103    public string ActorId { get; }
 104
 105    /// <inheritdoc />
 106    public GovernanceActorType ActorType { get; }
 107
 108    /// <inheritdoc />
 109    public string? ActorDisplayName { get; }
 110
 111    /// <inheritdoc />
 112    public string OperationName { get; }
 113
 114    /// <inheritdoc />
 115    public string Outcome { get; }
 116
 117    /// <inheritdoc />
 118    public IReadOnlyList<string> ReasonCodes { get; }
 119
 120    /// <inheritdoc />
 121    public string? CorrelationId { get; }
 122
 123    /// <inheritdoc />
 124    public string? TraceId { get; }
 125
 126    /// <inheritdoc />
 127    public string? SpanId { get; }
 128
 129    /// <inheritdoc />
 130    public string? ParentSpanId { get; }
 131
 132    /// <inheritdoc />
 133    public long? DecisionLatencyMs { get; }
 134
 135    /// <inheritdoc />
 136    public string? ConstraintSetHash { get; }
 137
 138    /// <inheritdoc />
 139    public int? ConstraintCount { get; }
 140
 141    /// <inheritdoc />
 142    public double? RiskScore { get; }
 143
 144    /// <inheritdoc />
 145    public string? PolicyScope { get; }
 146
 147    /// <inheritdoc />
 148    public string? TenantHash { get; }
 149
 150    /// <inheritdoc />
 151    public string? OrganizationHash { get; }
 152
 153    /// <inheritdoc />
 154    public string? EmitterStatus { get; }
 155
 156    /// <inheritdoc />
 157    public string? EmitterProvider { get; }
 158
 159    /// <inheritdoc />
 160    public long? OutboxSequence { get; }
 161
 162    /// <inheritdoc />
 163    public string? GatewayExecutionId { get; }
 164
 165    /// <inheritdoc />
 166    public string? DecisionStage { get; }
 167
 168    /// <inheritdoc />
 169    public string? PolicyVersion { get; }
 170
 171    /// <inheritdoc />
 172    public string? PolicyHash { get; }
 173
 174    /// <inheritdoc />
 175    public IReadOnlyDictionary<string, string> Metadata { get; }
 176
 177    /// <summary>
 178    /// Gets a value indicating whether this decision receipt contains reason codes.
 179    /// </summary>
 15180    public bool HasReasonCodes => ReasonCodes.Count > 0;
 181
 182    /// <summary>
 183    /// Gets a value indicating whether this decision receipt contains metadata.
 184    /// </summary>
 10185    public bool HasMetadata => Metadata.Count > 0;
 186
 187    /// <summary>
 188    /// Creates decision receipt from a host-defined operation outcome.
 189    /// </summary>
 190    /// <param name="actor">The actor associated with the operation.</param>
 191    /// <param name="operationName">The operation name.</param>
 192    /// <param name="outcome">The governance, constraint, or host-defined outcome.</param>
 193    /// <param name="reasonCodes">Optional machine-readable reason codes.</param>
 194    /// <param name="eventId">Optional audit event identifier. When omitted, a new identifier is generated.</param>
 195    /// <param name="occurredUtc">Optional event timestamp. When omitted, the current UTC timestamp is used.</param>
 196    /// <param name="correlationId">Optional correlation identifier.</param>
 197    /// <param name="traceId">Optional trace identifier.</param>
 198    /// <param name="policyVersion">Optional policy version.</param>
 199    /// <param name="policyHash">Optional policy hash.</param>
 200    /// <param name="metadata">Optional host-provided audit metadata.</param>
 201    /// <param name="decisionReceiptId">Optional decision receipt identifier. When omitted, the normalized event identif
 202    /// <param name="spanId">Optional span identifier.</param>
 203    /// <param name="parentSpanId">Optional parent span identifier.</param>
 204    /// <param name="decisionLatencyMs">Optional decision latency in milliseconds.</param>
 205    /// <param name="constraintSetHash">Optional evaluated constraint-set hash.</param>
 206    /// <param name="constraintCount">Optional evaluated constraint count.</param>
 207    /// <param name="riskScore">Optional host-defined risk score.</param>
 208    /// <param name="policyScope">Optional host-defined policy scope.</param>
 209    /// <param name="tenantHash">Optional privacy-preserving tenant hash.</param>
 210    /// <param name="organizationHash">Optional privacy-preserving organization hash.</param>
 211    /// <param name="emitterStatus">Optional provider-neutral emitter status.</param>
 212    /// <param name="emitterProvider">Optional provider-neutral emitter provider name.</param>
 213    /// <param name="outboxSequence">Optional outbox sequence.</param>
 214    /// <param name="gatewayExecutionId">Optional gateway execution identifier.</param>
 215    /// <param name="decisionStage">Optional provider-neutral decision stage.</param>
 216    /// <param name="schemaVersion">Optional schema version. When omitted, the stable artifact schema version is used.</
 217    /// <returns>An decision receipt value.</returns>
 218    public static DecisionReceipt Create(
 219        IGovernanceActorContext actor,
 220        string operationName,
 221        string outcome,
 222        IEnumerable<string>? reasonCodes = null,
 223        string? eventId = null,
 224        DateTimeOffset? occurredUtc = null,
 225        string? correlationId = null,
 226        string? traceId = null,
 227        string? policyVersion = null,
 228        string? policyHash = null,
 229        IReadOnlyDictionary<string, string>? metadata = null,
 230        string? decisionReceiptId = null,
 231        string? spanId = null,
 232        string? parentSpanId = null,
 233        long? decisionLatencyMs = null,
 234        string? constraintSetHash = null,
 235        int? constraintCount = null,
 236        double? riskScore = null,
 237        string? policyScope = null,
 238        string? tenantHash = null,
 239        string? organizationHash = null,
 240        string? emitterStatus = null,
 241        string? emitterProvider = null,
 242        long? outboxSequence = null,
 243        string? gatewayExecutionId = null,
 244        string? decisionStage = null,
 245        string? schemaVersion = null)
 246    {
 148247        return CreateCore(
 148248            actor,
 148249            operationName,
 148250            outcome,
 148251            NormalizeReasonCodes(reasonCodes),
 148252            eventId,
 148253            occurredUtc,
 148254            correlationId,
 148255            traceId,
 148256            policyVersion,
 148257            policyHash,
 148258            metadata,
 148259            decisionReceiptId,
 148260            spanId,
 148261            parentSpanId,
 148262            decisionLatencyMs,
 148263            constraintSetHash,
 148264            constraintCount,
 148265            riskScore,
 148266            policyScope,
 148267            tenantHash,
 148268            organizationHash,
 148269            emitterStatus,
 148270            emitterProvider,
 148271            outboxSequence,
 148272            gatewayExecutionId,
 148273            decisionStage,
 148274            schemaVersion);
 275    }
 276
 277    /// <summary>
 278    /// Creates decision receipt from a governance decision.
 279    /// </summary>
 280    /// <param name="actor">The actor associated with the operation.</param>
 281    /// <param name="operationName">The operation name.</param>
 282    /// <param name="decision">The governance decision to audit.</param>
 283    /// <param name="eventId">Optional audit event identifier. When omitted, a new identifier is generated.</param>
 284    /// <param name="occurredUtc">Optional event timestamp. When omitted, the current UTC timestamp is used.</param>
 285    /// <param name="metadata">Optional host-provided audit metadata.</param>
 286    /// <param name="decisionReceiptId">Optional decision receipt identifier. When omitted, the normalized event identif
 287    /// <param name="spanId">Optional span identifier.</param>
 288    /// <param name="parentSpanId">Optional parent span identifier.</param>
 289    /// <param name="decisionLatencyMs">Optional decision latency in milliseconds.</param>
 290    /// <param name="constraintSetHash">Optional evaluated constraint-set hash.</param>
 291    /// <param name="constraintCount">Optional evaluated constraint count.</param>
 292    /// <param name="riskScore">Optional host-defined risk score.</param>
 293    /// <param name="policyScope">Optional host-defined policy scope.</param>
 294    /// <param name="tenantHash">Optional privacy-preserving tenant hash.</param>
 295    /// <param name="organizationHash">Optional privacy-preserving organization hash.</param>
 296    /// <param name="emitterStatus">Optional provider-neutral emitter status.</param>
 297    /// <param name="emitterProvider">Optional provider-neutral emitter provider name.</param>
 298    /// <param name="outboxSequence">Optional outbox sequence.</param>
 299    /// <param name="gatewayExecutionId">Optional gateway execution identifier.</param>
 300    /// <param name="decisionStage">Optional provider-neutral decision stage.</param>
 301    /// <param name="schemaVersion">Optional schema version. When omitted, the stable artifact schema version is used.</
 302    /// <returns>An decision receipt value.</returns>
 303    public static DecisionReceipt FromDecision(
 304        IGovernanceActorContext actor,
 305        string operationName,
 306        GovernanceDecision decision,
 307        string? eventId = null,
 308        DateTimeOffset? occurredUtc = null,
 309        IReadOnlyDictionary<string, string>? metadata = null,
 310        string? decisionReceiptId = null,
 311        string? spanId = null,
 312        string? parentSpanId = null,
 313        long? decisionLatencyMs = null,
 314        string? constraintSetHash = null,
 315        int? constraintCount = null,
 316        double? riskScore = null,
 317        string? policyScope = null,
 318        string? tenantHash = null,
 319        string? organizationHash = null,
 320        string? emitterStatus = null,
 321        string? emitterProvider = null,
 322        long? outboxSequence = null,
 323        string? gatewayExecutionId = null,
 324        string? decisionStage = null,
 325        string? schemaVersion = null)
 326    {
 20327        ArgumentNullException.ThrowIfNull(decision);
 328
 19329        return CreateCore(
 19330            actor,
 19331            operationName,
 19332            CanonicalEnumWireNames.ForDecisionOutcome(decision.Outcome),
 19333            UseTrustedReasonCodes(decision.ReasonCodes),
 19334            eventId,
 19335            occurredUtc,
 19336            decision.CorrelationId,
 19337            decision.TraceId,
 19338            decision.PolicyVersion,
 19339            decision.PolicyHash,
 19340            metadata,
 19341            decisionReceiptId,
 19342            spanId,
 19343            parentSpanId,
 19344            decisionLatencyMs,
 19345            constraintSetHash,
 19346            constraintCount,
 19347            riskScore,
 19348            policyScope,
 19349            tenantHash,
 19350            organizationHash,
 19351            emitterStatus,
 19352            emitterProvider,
 19353            outboxSequence,
 19354            gatewayExecutionId,
 19355            decisionStage,
 19356            schemaVersion);
 357    }
 358
 359    /// <summary>
 360    /// Creates decision receipt from a constraint evaluation result.
 361    /// </summary>
 362    /// <param name="actor">The actor associated with the operation.</param>
 363    /// <param name="operationName">The operation name.</param>
 364    /// <param name="constraintResult">The constraint evaluation result to audit.</param>
 365    /// <param name="eventId">Optional audit event identifier. When omitted, a new identifier is generated.</param>
 366    /// <param name="occurredUtc">Optional event timestamp. When omitted, the current UTC timestamp is used.</param>
 367    /// <param name="correlationId">Optional correlation identifier.</param>
 368    /// <param name="traceId">Optional trace identifier.</param>
 369    /// <param name="policyVersion">Optional policy version.</param>
 370    /// <param name="policyHash">Optional policy hash.</param>
 371    /// <param name="metadata">Optional host-provided audit metadata.</param>
 372    /// <param name="decisionReceiptId">Optional decision receipt identifier. When omitted, the normalized event identif
 373    /// <param name="spanId">Optional span identifier.</param>
 374    /// <param name="parentSpanId">Optional parent span identifier.</param>
 375    /// <param name="decisionLatencyMs">Optional decision latency in milliseconds.</param>
 376    /// <param name="constraintSetHash">Optional evaluated constraint-set hash.</param>
 377    /// <param name="constraintCount">Optional evaluated constraint count.</param>
 378    /// <param name="riskScore">Optional host-defined risk score.</param>
 379    /// <param name="policyScope">Optional host-defined policy scope.</param>
 380    /// <param name="tenantHash">Optional privacy-preserving tenant hash.</param>
 381    /// <param name="organizationHash">Optional privacy-preserving organization hash.</param>
 382    /// <param name="emitterStatus">Optional provider-neutral emitter status.</param>
 383    /// <param name="emitterProvider">Optional provider-neutral emitter provider name.</param>
 384    /// <param name="outboxSequence">Optional outbox sequence.</param>
 385    /// <param name="gatewayExecutionId">Optional gateway execution identifier.</param>
 386    /// <param name="decisionStage">Optional provider-neutral decision stage.</param>
 387    /// <param name="schemaVersion">Optional schema version. When omitted, the stable artifact schema version is used.</
 388    /// <returns>An decision receipt value.</returns>
 389    public static DecisionReceipt FromConstraint(
 390        IGovernanceActorContext actor,
 391        string operationName,
 392        ConstraintEvaluationResult constraintResult,
 393        string? eventId = null,
 394        DateTimeOffset? occurredUtc = null,
 395        string? correlationId = null,
 396        string? traceId = null,
 397        string? policyVersion = null,
 398        string? policyHash = null,
 399        IReadOnlyDictionary<string, string>? metadata = null,
 400        string? decisionReceiptId = null,
 401        string? spanId = null,
 402        string? parentSpanId = null,
 403        long? decisionLatencyMs = null,
 404        string? constraintSetHash = null,
 405        int? constraintCount = null,
 406        double? riskScore = null,
 407        string? policyScope = null,
 408        string? tenantHash = null,
 409        string? organizationHash = null,
 410        string? emitterStatus = null,
 411        string? emitterProvider = null,
 412        long? outboxSequence = null,
 413        string? gatewayExecutionId = null,
 414        string? decisionStage = null,
 415        string? schemaVersion = null)
 416    {
 12417        ArgumentNullException.ThrowIfNull(constraintResult);
 418
 11419        return CreateCore(
 11420            actor,
 11421            operationName,
 11422            CanonicalEnumWireNames.ForConstraintOutcome(constraintResult.Outcome),
 11423            UseTrustedReasonCodes(constraintResult.ReasonCodes),
 11424            eventId,
 11425            occurredUtc,
 11426            correlationId,
 11427            traceId,
 11428            policyVersion,
 11429            policyHash,
 11430            metadata,
 11431            decisionReceiptId,
 11432            spanId,
 11433            parentSpanId,
 11434            decisionLatencyMs,
 11435            constraintSetHash,
 11436            constraintCount,
 11437            riskScore,
 11438            policyScope,
 11439            tenantHash,
 11440            organizationHash,
 11441            emitterStatus,
 11442            emitterProvider,
 11443            outboxSequence,
 11444            gatewayExecutionId,
 11445            decisionStage,
 11446            schemaVersion);
 447    }
 448
 449    private static DecisionReceipt CreateCore(
 450        IGovernanceActorContext actor,
 451        string operationName,
 452        string outcome,
 453        IReadOnlyList<string> reasonCodes,
 454        string? eventId,
 455        DateTimeOffset? occurredUtc,
 456        string? correlationId,
 457        string? traceId,
 458        string? policyVersion,
 459        string? policyHash,
 460        IReadOnlyDictionary<string, string>? metadata,
 461        string? decisionReceiptId,
 462        string? spanId,
 463        string? parentSpanId,
 464        long? decisionLatencyMs,
 465        string? constraintSetHash,
 466        int? constraintCount,
 467        double? riskScore,
 468        string? policyScope,
 469        string? tenantHash,
 470        string? organizationHash,
 471        string? emitterStatus,
 472        string? emitterProvider,
 473        long? outboxSequence,
 474        string? gatewayExecutionId,
 475        string? decisionStage,
 476        string? schemaVersion)
 477    {
 178478        ArgumentNullException.ThrowIfNull(actor);
 479
 177480        string normalizedEventId = NormalizeIdentifier(eventId);
 481
 177482        return new DecisionReceipt(
 177483            normalizedEventId,
 177484            NormalizeDecisionReceiptId(decisionReceiptId, normalizedEventId),
 177485            schemaVersion ?? GovernanceSchemaVersions.StableArtifactsV1,
 177486            occurredUtc ?? DateTimeOffset.UtcNow,
 177487            actor.ActorId,
 177488            actor.ActorType,
 177489            actor.DisplayName,
 177490            operationName,
 177491            outcome,
 177492            reasonCodes,
 177493            correlationId,
 177494            traceId,
 177495            spanId,
 177496            parentSpanId,
 177497            decisionLatencyMs,
 177498            constraintSetHash,
 177499            constraintCount,
 177500            riskScore,
 177501            policyScope,
 177502            tenantHash,
 177503            organizationHash,
 177504            emitterStatus,
 177505            emitterProvider,
 177506            outboxSequence,
 177507            gatewayExecutionId,
 177508            decisionStage,
 177509            policyVersion,
 177510            policyHash,
 177511            NormalizeMetadata(metadata));
 512    }
 513
 514    private static string NormalizeIdentifier(string? identifier)
 515    {
 177516        return string.IsNullOrWhiteSpace(identifier)
 177517            ? Guid.NewGuid().ToString("N")
 177518            : identifier.Trim();
 519    }
 520
 521    private static string NormalizeDecisionReceiptId(string? decisionReceiptId, string eventId)
 522    {
 177523        return string.IsNullOrWhiteSpace(decisionReceiptId)
 177524            ? eventId
 177525            : decisionReceiptId.Trim();
 526    }
 527
 528    private static string? NormalizeOptional(string? value)
 529    {
 2418530        return string.IsNullOrWhiteSpace(value)
 2418531            ? null
 2418532            : value.Trim();
 533    }
 534
 535    private static long? NormalizeNonNegative(long? value, string parameterName)
 536    {
 327537        return value < 0
 327538            ? throw new ArgumentOutOfRangeException(parameterName, value, "Value must be greater than or equal to zero."
 327539            : value;
 540    }
 541
 542    private static int? NormalizeNonNegative(int? value, string parameterName)
 543    {
 167544        return value < 0
 167545            ? throw new ArgumentOutOfRangeException(parameterName, value, "Value must be greater than or equal to zero."
 167546            : value;
 547    }
 548
 549    private static double? NormalizeRiskScore(double? riskScore)
 550    {
 164551        return riskScore is null
 164552            ? null
 164553            : double.IsNaN(riskScore.Value) || double.IsInfinity(riskScore.Value) || riskScore.Value < 0
 164554            ? throw new ArgumentOutOfRangeException(nameof(riskScore), riskScore, "Risk score must be a finite value gre
 164555            : riskScore;
 556    }
 557
 558    private static ReadOnlyCollection<string> NormalizeReasonCodes(IEnumerable<string>? reasonCodes)
 559    {
 148560        if (reasonCodes is null)
 561        {
 69562            return EmptyReasonCodes;
 563        }
 564
 79565        if (reasonCodes is ICollection<string> collection)
 566        {
 77567            if (collection.Count == 0)
 568            {
 16569                return EmptyReasonCodes;
 570            }
 571
 61572            string[] normalizedReasonCodes = new string[collection.Count];
 61573            int normalizedCount = 0;
 574
 288575            foreach (string? reasonCode in collection)
 576            {
 83577                AddNormalizedReasonCode(reasonCode, normalizedReasonCodes, ref normalizedCount);
 578            }
 579
 61580            return CreateReasonCodeCollection(normalizedReasonCodes, normalizedCount);
 581        }
 582
 2583        List<string>? normalizedList = null;
 584
 16585        foreach (string? reasonCode in reasonCodes)
 586        {
 6587            if (string.IsNullOrWhiteSpace(reasonCode))
 588            {
 589                continue;
 590            }
 591
 2592            normalizedList ??= [];
 2593            normalizedList.Add(reasonCode.Trim());
 594        }
 595
 2596        return normalizedList is null || normalizedList.Count == 0
 2597            ? EmptyReasonCodes
 2598            : Array.AsReadOnly([.. normalizedList]);
 599    }
 600
 601    private static IReadOnlyList<string> UseTrustedReasonCodes(IReadOnlyList<string> reasonCodes)
 602    {
 30603        return reasonCodes.Count == 0
 30604            ? EmptyReasonCodes
 30605            : reasonCodes;
 606    }
 607
 608    private static void AddNormalizedReasonCode(
 609        string? reasonCode,
 610        string[] normalizedReasonCodes,
 611        ref int normalizedCount)
 612    {
 83613        if (string.IsNullOrWhiteSpace(reasonCode))
 614        {
 10615            return;
 616        }
 617
 73618        normalizedReasonCodes[normalizedCount] = reasonCode.Trim();
 73619        normalizedCount++;
 73620    }
 621
 622    private static ReadOnlyCollection<string> CreateReasonCodeCollection(
 623        string[] normalizedReasonCodes,
 624        int normalizedCount)
 625    {
 61626        if (normalizedCount == 0)
 627        {
 1628            return EmptyReasonCodes;
 629        }
 630
 60631        if (normalizedCount == normalizedReasonCodes.Length)
 632        {
 55633            return Array.AsReadOnly(normalizedReasonCodes);
 634        }
 635
 5636        string[] filteredReasonCodes = new string[normalizedCount];
 5637        Array.Copy(normalizedReasonCodes, filteredReasonCodes, normalizedCount);
 638
 5639        return Array.AsReadOnly(filteredReasonCodes);
 640    }
 641
 642    private static IReadOnlyDictionary<string, string> NormalizeMetadata(
 643        IReadOnlyDictionary<string, string>? metadata)
 644    {
 177645        if (metadata is null || metadata.Count == 0)
 646        {
 137647            return EmptyMetadata;
 648        }
 649
 40650        Dictionary<string, string> normalizedMetadata = new(metadata.Count, StringComparer.Ordinal);
 651
 213652        foreach (KeyValuePair<string, string> item in metadata)
 653        {
 67654            if (string.IsNullOrWhiteSpace(item.Key))
 655            {
 656                continue;
 657            }
 658
 60659            string normalizedKey = item.Key.Trim();
 60660            if (normalizedMetadata.ContainsKey(normalizedKey))
 661            {
 1662                throw new ArgumentException(
 1663                    $"Decision receipt metadata contains keys that collide after trimming: '{normalizedKey}'.",
 1664                    nameof(metadata));
 665            }
 666
 59667            normalizedMetadata[normalizedKey] = item.Value?.Trim() ?? string.Empty;
 668        }
 669
 39670        return normalizedMetadata.Count == 0
 39671            ? EmptyMetadata
 39672            : new ReadOnlyDictionary<string, string>(normalizedMetadata);
 673    }
 674}

Methods/Properties

.cctor()
.ctor(System.String,System.String,System.String,System.DateTimeOffset,System.String,AsiBackbone.Core.Actors.GovernanceActorType,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyList`1<System.String>,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>,System.String,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
get_HasReasonCodes()
get_HasMetadata()
Create(AsiBackbone.Core.Actors.IGovernanceActorContext,System.String,System.String,System.Collections.Generic.IEnumerable`1<System.String>,System.String,System.Nullable`1<System.DateTimeOffset>,System.String,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>,System.String,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.String,System.String)
FromDecision(AsiBackbone.Core.Actors.IGovernanceActorContext,System.String,AsiBackbone.Core.Decisions.GovernanceDecision,System.String,System.Nullable`1<System.DateTimeOffset>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>,System.String,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.String,System.String)
FromConstraint(AsiBackbone.Core.Actors.IGovernanceActorContext,System.String,AsiBackbone.Core.Constraints.ConstraintEvaluationResult,System.String,System.Nullable`1<System.DateTimeOffset>,System.String,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>,System.String,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.String,System.String)
CreateCore(AsiBackbone.Core.Actors.IGovernanceActorContext,System.String,System.String,System.Collections.Generic.IReadOnlyList`1<System.String>,System.String,System.Nullable`1<System.DateTimeOffset>,System.String,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>,System.String,System.String,System.String,System.String,System.String,System.Nullable`1<System.Int64>,System.String,System.String,System.String)
NormalizeIdentifier(System.String)
NormalizeDecisionReceiptId(System.String,System.String)
NormalizeOptional(System.String)
NormalizeNonNegative(System.Nullable`1<System.Int64>,System.String)
NormalizeNonNegative(System.Nullable`1<System.Int32>,System.String)
NormalizeRiskScore(System.Nullable`1<System.Double>)
NormalizeReasonCodes(System.Collections.Generic.IEnumerable`1<System.String>)
UseTrustedReasonCodes(System.Collections.Generic.IReadOnlyList`1<System.String>)
AddNormalizedReasonCode(System.String,System.String[],System.Int32&)
CreateReasonCodeCollection(System.String[],System.Int32)
NormalizeMetadata(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)