< 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
94%
Covered lines: 102
Uncovered lines: 6
Coverable lines: 108
Total lines: 213
Line coverage: 94.4%
Branch coverage
84%
Covered branches: 32
Total branches: 38
Branch coverage: 84.2%
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_CanProceed()100%11100%
get_IsFailOpen()100%22100%
get_IsFailClosed()100%11100%
Create(...)83.33%121296.55%
BuildMessage(...)87.5%8890.91%
BuildReasonMetadata(...)81.25%1616100%
ToMetadataValue(...)100%11100%

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>
 32    public DlpFailurePolicyContext Context { get; }
 33
 34    /// <summary>
 35    /// Gets the resolved provider-neutral failure behavior.
 36    /// </summary>
 37    public DlpFailureBehavior Behavior { get; }
 38
 39    /// <summary>
 40    /// Gets the machine-readable reason associated with the failure behavior.
 41    /// </summary>
 42    public OperationReason Reason { get; }
 43
 44    /// <summary>
 45    /// Gets the governance decision produced by the resolved behavior.
 46    /// </summary>
 47    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    {
 3374        ArgumentNullException.ThrowIfNull(context);
 75
 3276        if (!Enum.IsDefined(behavior))
 77        {
 178            throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined.");
 79        }
 80
 81        // A resolution carries the governance decision a host acts on, so an unconfigured behavior cannot be materializ
 82        // into one. Rejecting it here rather than in the switch below names the actual problem instead of reporting the
 83        // value as merely undefined.
 3184        if (behavior == DlpFailureBehavior.Unspecified)
 85        {
 186            throw new ArgumentOutOfRangeException(
 187                nameof(behavior),
 188                behavior,
 189                "DLP failure behavior must be resolved before a policy resolution is created. Unspecified is not an acti
 90        }
 91
 3092        var reason = OperationReason.Create(
 3093            DlpFailureReasonCodes.GetFor(context.FailureKind),
 3094            BuildMessage(context),
 3095            BuildReasonMetadata(context, behavior));
 96
 3097        GovernanceDecision decision = behavior switch
 3098        {
 399            DlpFailureBehavior.Allow => GovernanceDecision.Allow(
 3100                correlationId: context.CorrelationId,
 3101                traceId: context.TraceId,
 3102                policyVersion: context.PolicyVersion,
 3103                policyHash: context.PolicyHash),
 17104            DlpFailureBehavior.WarnAndAllow => GovernanceDecision.Warning(
 17105                reason,
 17106                correlationId: context.CorrelationId,
 17107                traceId: context.TraceId,
 17108                policyVersion: context.PolicyVersion,
 17109                policyHash: context.PolicyHash),
 2110            DlpFailureBehavior.Deny => GovernanceDecision.Deny(
 2111                reason,
 2112                correlationId: context.CorrelationId,
 2113                traceId: context.TraceId,
 2114                policyVersion: context.PolicyVersion,
 2115                policyHash: context.PolicyHash),
 3116            DlpFailureBehavior.Defer => GovernanceDecision.Defer(
 3117                reason.Code,
 3118                reason.Message,
 3119                correlationId: context.CorrelationId,
 3120                traceId: context.TraceId,
 3121                policyVersion: context.PolicyVersion,
 3122                policyHash: context.PolicyHash),
 2123            DlpFailureBehavior.RequireAcknowledgment => GovernanceDecision.RequireAcknowledgment(
 2124                reason.Code,
 2125                reason.Message,
 2126                correlationId: context.CorrelationId,
 2127                traceId: context.TraceId,
 2128                policyVersion: context.PolicyVersion,
 2129                policyHash: context.PolicyHash),
 3130            DlpFailureBehavior.Escalate => GovernanceDecision.Escalate(
 3131                reason.Code,
 3132                reason.Message,
 3133                correlationId: context.CorrelationId,
 3134                traceId: context.TraceId,
 3135                policyVersion: context.PolicyVersion,
 3136                policyHash: context.PolicyHash),
 30137            // Rejected above, so this arm is unreachable. It stays explicit so that an unconfigured behavior can never
 30138            // acquire a governance decision by falling through to a default.
 0139            DlpFailureBehavior.Unspecified => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP fai
 0140            _ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined
 30141        };
 142
 30143        return new DlpFailurePolicyResolution(context, behavior, reason, decision);
 144    }
 145
 146    private static string BuildMessage(DlpFailurePolicyContext context)
 147    {
 30148        return context.FailureKind switch
 30149        {
 17150            DlpClassificationFailureKind.ServiceUnavailable => "DLP/classification screening service was unavailable.",
 4151            DlpClassificationFailureKind.Timeout => context.Timeout.HasValue
 4152                ? $"DLP/classification screening timed out after {context.Timeout.Value.TotalMilliseconds:0} ms."
 4153                : "DLP/classification screening timed out.",
 3154            DlpClassificationFailureKind.IndeterminateResult => "DLP/classification screening returned an indeterminate 
 3155            DlpClassificationFailureKind.BlockedResult => "DLP/classification screening returned a blocked result.",
 3156            DlpClassificationFailureKind.ClassifiedResult => "DLP/classification screening returned a classified result 
 0157            _ => throw new ArgumentOutOfRangeException(nameof(context), context.FailureKind, "DLP failure kind must be d
 30158        };
 159    }
 160
 161    private static IReadOnlyDictionary<string, string> BuildReasonMetadata(
 162        DlpFailurePolicyContext context,
 163        DlpFailureBehavior behavior)
 164    {
 30165        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 166
 66167        foreach (KeyValuePair<string, string> item in context.Metadata)
 168        {
 3169            if (string.IsNullOrWhiteSpace(item.Key))
 170            {
 171                continue;
 172            }
 173
 3174            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 175        }
 176
 30177        metadata["dlp.failure_kind"] = ToMetadataValue(context.FailureKind);
 30178        metadata["dlp.risk_level"] = ToMetadataValue(context.RiskLevel);
 30179        metadata["dlp.behavior"] = ToMetadataValue(behavior);
 180
 30181        if (!string.IsNullOrWhiteSpace(context.IntentCategory))
 182        {
 2183            metadata["dlp.intent_category"] = context.IntentCategory;
 184        }
 185
 30186        if (!string.IsNullOrWhiteSpace(context.Environment))
 187        {
 2188            metadata["dlp.environment"] = context.Environment;
 189        }
 190
 30191        if (context.Timeout.HasValue)
 192        {
 1193            metadata["dlp.timeout_ms"] = context.Timeout.Value.TotalMilliseconds
 1194                .ToString("0", CultureInfo.InvariantCulture);
 195        }
 196
 30197        return metadata.Count == 0
 30198            ? EmptyMetadata
 30199            : new ReadOnlyDictionary<string, string>(metadata);
 200    }
 201
 202    private static string ToMetadataValue<TEnum>(TEnum value)
 203        where TEnum : struct, Enum
 204    {
 90205        string text = value.ToString();
 206
 90207        return string.Concat(
 90208            text.Select((character, index) =>
 90209                index > 0 && char.IsUpper(character)
 90210                    ? "_" + char.ToLowerInvariant(character)
 90211                    : char.ToLowerInvariant(character).ToString()));
 212    }
 213}