< Summary

Information
Class: AsiBackbone.Core.Classification.DlpFailurePolicyResolution
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Classification/DlpFailurePolicyResolution.cs
Line coverage
95%
Covered lines: 99
Uncovered lines: 5
Coverable lines: 104
Total lines: 199
Line coverage: 95.1%
Branch coverage
87%
Covered branches: 36
Total branches: 41
Branch coverage: 87.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%11100%
get_Context()100%11100%
get_Behavior()100%11100%
get_Reason()100%11100%
get_Decision()100%11100%
get_CanProceed()100%11100%
get_IsFailOpen()100%22100%
get_IsFailClosed()100%11100%
Create(...)88.88%9998%
BuildMessage(...)87.5%8890.9%
BuildReasonMetadata(...)81.25%1616100%
ToMetadataValue(...)100%66100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Classification/DlpFailurePolicyResolution.cs

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using System.Globalization;
 3using AsiBackbone.Core.Decisions;
 4using AsiBackbone.Core.Results;
 5
 6namespace AsiBackbone.Core.Classification;
 7
 8/// <summary>
 9/// Represents the resolved provider-neutral policy response to a DLP or classification failure.
 10/// </summary>
 11public sealed class DlpFailurePolicyResolution
 12{
 013    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 014        new ReadOnlyDictionary<string, string>(
 015            new Dictionary<string, string>(StringComparer.Ordinal));
 16
 3017    private DlpFailurePolicyResolution(
 3018        DlpFailurePolicyContext context,
 3019        DlpFailureBehavior behavior,
 3020        OperationReason reason,
 3021        GovernanceDecision decision)
 22    {
 3023        Context = context;
 3024        Behavior = behavior;
 3025        Reason = reason;
 3026        Decision = decision;
 3027    }
 28
 29    /// <summary>
 30    /// Gets the original failure context.
 31    /// </summary>
 632    public DlpFailurePolicyContext Context { get; }
 33
 34    /// <summary>
 35    /// Gets the resolved provider-neutral failure behavior.
 36    /// </summary>
 3137    public DlpFailureBehavior Behavior { get; }
 38
 39    /// <summary>
 40    /// Gets the machine-readable reason associated with the failure behavior.
 41    /// </summary>
 7742    public OperationReason Reason { get; }
 43
 44    /// <summary>
 45    /// Gets the governance decision produced by the resolved behavior.
 46    /// </summary>
 10347    public GovernanceDecision Decision { get; }
 48
 49    /// <summary>
 50    /// Gets a value indicating whether the resolved decision allows immediate execution.
 51    /// </summary>
 652    public bool CanProceed => Decision.CanProceed;
 53
 54    /// <summary>
 55    /// Gets a value indicating whether the resolution uses a fail-open behavior.
 56    /// </summary>
 857    public bool IsFailOpen => Behavior is DlpFailureBehavior.Allow or DlpFailureBehavior.WarnAndAllow;
 58
 59    /// <summary>
 60    /// Gets a value indicating whether the resolution uses a fail-closed behavior.
 61    /// </summary>
 862    public bool IsFailClosed => Behavior is DlpFailureBehavior.Deny;
 63
 64    /// <summary>
 65    /// Creates a policy resolution for the supplied context and behavior.
 66    /// </summary>
 67    /// <param name="context">The original DLP or classification failure context.</param>
 68    /// <param name="behavior">The resolved failure behavior.</param>
 69    /// <returns>The policy resolution.</returns>
 70    public static DlpFailurePolicyResolution Create(
 71        DlpFailurePolicyContext context,
 72        DlpFailureBehavior behavior)
 73    {
 3274        ArgumentNullException.ThrowIfNull(context);
 75
 3176        if (!Enum.IsDefined(behavior))
 77        {
 178            throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined.");
 79        }
 80
 3081        var reason = OperationReason.Create(
 3082            DlpFailureReasonCodes.GetFor(context.FailureKind),
 3083            BuildMessage(context),
 3084            BuildReasonMetadata(context, behavior));
 85
 3086        GovernanceDecision decision = behavior switch
 3087        {
 388            DlpFailureBehavior.Allow => GovernanceDecision.Allow(
 389                correlationId: context.CorrelationId,
 390                traceId: context.TraceId,
 391                policyVersion: context.PolicyVersion,
 392                policyHash: context.PolicyHash),
 1793            DlpFailureBehavior.WarnAndAllow => GovernanceDecision.Warning(
 1794                reason,
 1795                correlationId: context.CorrelationId,
 1796                traceId: context.TraceId,
 1797                policyVersion: context.PolicyVersion,
 1798                policyHash: context.PolicyHash),
 299            DlpFailureBehavior.Deny => GovernanceDecision.Deny(
 2100                reason,
 2101                correlationId: context.CorrelationId,
 2102                traceId: context.TraceId,
 2103                policyVersion: context.PolicyVersion,
 2104                policyHash: context.PolicyHash),
 3105            DlpFailureBehavior.Defer => GovernanceDecision.Defer(
 3106                reason.Code,
 3107                reason.Message,
 3108                correlationId: context.CorrelationId,
 3109                traceId: context.TraceId,
 3110                policyVersion: context.PolicyVersion,
 3111                policyHash: context.PolicyHash),
 2112            DlpFailureBehavior.RequireAcknowledgment => GovernanceDecision.RequireAcknowledgment(
 2113                reason.Code,
 2114                reason.Message,
 2115                correlationId: context.CorrelationId,
 2116                traceId: context.TraceId,
 2117                policyVersion: context.PolicyVersion,
 2118                policyHash: context.PolicyHash),
 3119            DlpFailureBehavior.Escalate => GovernanceDecision.Escalate(
 3120                reason.Code,
 3121                reason.Message,
 3122                correlationId: context.CorrelationId,
 3123                traceId: context.TraceId,
 3124                policyVersion: context.PolicyVersion,
 3125                policyHash: context.PolicyHash),
 0126            _ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined
 30127        };
 128
 30129        return new DlpFailurePolicyResolution(context, behavior, reason, decision);
 130    }
 131
 132    private static string BuildMessage(DlpFailurePolicyContext context)
 133    {
 30134        return context.FailureKind switch
 30135        {
 17136            DlpClassificationFailureKind.ServiceUnavailable => "DLP/classification screening service was unavailable.",
 4137            DlpClassificationFailureKind.Timeout => context.Timeout.HasValue
 4138                ? $"DLP/classification screening timed out after {context.Timeout.Value.TotalMilliseconds:0} ms."
 4139                : "DLP/classification screening timed out.",
 3140            DlpClassificationFailureKind.IndeterminateResult => "DLP/classification screening returned an indeterminate 
 3141            DlpClassificationFailureKind.BlockedResult => "DLP/classification screening returned a blocked result.",
 3142            DlpClassificationFailureKind.ClassifiedResult => "DLP/classification screening returned a classified result 
 0143            _ => throw new ArgumentOutOfRangeException(nameof(context), context.FailureKind, "DLP failure kind must be d
 30144        };
 145    }
 146
 147    private static IReadOnlyDictionary<string, string> BuildReasonMetadata(
 148        DlpFailurePolicyContext context,
 149        DlpFailureBehavior behavior)
 150    {
 30151        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 152
 66153        foreach (KeyValuePair<string, string> item in context.Metadata)
 154        {
 3155            if (string.IsNullOrWhiteSpace(item.Key))
 156            {
 157                continue;
 158            }
 159
 3160            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 161        }
 162
 30163        metadata["dlp.failure_kind"] = ToMetadataValue(context.FailureKind);
 30164        metadata["dlp.risk_level"] = ToMetadataValue(context.RiskLevel);
 30165        metadata["dlp.behavior"] = ToMetadataValue(behavior);
 166
 30167        if (!string.IsNullOrWhiteSpace(context.IntentCategory))
 168        {
 2169            metadata["dlp.intent_category"] = context.IntentCategory;
 170        }
 171
 30172        if (!string.IsNullOrWhiteSpace(context.Environment))
 173        {
 2174            metadata["dlp.environment"] = context.Environment;
 175        }
 176
 30177        if (context.Timeout.HasValue)
 178        {
 1179            metadata["dlp.timeout_ms"] = context.Timeout.Value.TotalMilliseconds
 1180                .ToString("0", CultureInfo.InvariantCulture);
 181        }
 182
 30183        return metadata.Count == 0
 30184            ? EmptyMetadata
 30185            : new ReadOnlyDictionary<string, string>(metadata);
 186    }
 187
 188    private static string ToMetadataValue<TEnum>(TEnum value)
 189        where TEnum : struct, Enum
 190    {
 90191        string text = value.ToString();
 192
 90193        return string.Concat(
 90194            text.Select((character, index) =>
 978195                index > 0 && char.IsUpper(character)
 978196                    ? "_" + char.ToLowerInvariant(character)
 978197                    : char.ToLowerInvariant(character).ToString()));
 198    }
 199}