< 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
 6017    private DlpFailurePolicyResolution(
 6018        DlpFailurePolicyContext context,
 6019        DlpFailureBehavior behavior,
 6020        OperationReason reason,
 6021        GovernanceDecision decision)
 22    {
 6023        Context = context;
 6024        Behavior = behavior;
 6025        Reason = reason;
 6026        Decision = decision;
 6027    }
 28
 29    /// <summary>
 30    /// Gets the original failure context.
 31    /// </summary>
 1232    public DlpFailurePolicyContext Context { get; }
 33
 34    /// <summary>
 35    /// Gets the resolved provider-neutral failure behavior.
 36    /// </summary>
 6237    public DlpFailureBehavior Behavior { get; }
 38
 39    /// <summary>
 40    /// Gets the machine-readable reason associated with the failure behavior.
 41    /// </summary>
 15442    public OperationReason Reason { get; }
 43
 44    /// <summary>
 45    /// Gets the governance decision produced by the resolved behavior.
 46    /// </summary>
 20647    public GovernanceDecision Decision { get; }
 48
 49    /// <summary>
 50    /// Gets a value indicating whether the resolved decision allows immediate execution.
 51    /// </summary>
 1252    public bool CanProceed => Decision.CanProceed;
 53
 54    /// <summary>
 55    /// Gets a value indicating whether the resolution uses a fail-open behavior.
 56    /// </summary>
 1657    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>
 1662    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    {
 6474        ArgumentNullException.ThrowIfNull(context);
 75
 6276        if (!Enum.IsDefined(behavior))
 77        {
 278            throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined.");
 79        }
 80
 6081        var reason = OperationReason.Create(
 6082            DlpFailureReasonCodes.GetFor(context.FailureKind),
 6083            BuildMessage(context),
 6084            BuildReasonMetadata(context, behavior));
 85
 6086        GovernanceDecision decision = behavior switch
 6087        {
 688            DlpFailureBehavior.Allow => GovernanceDecision.Allow(
 689                correlationId: context.CorrelationId,
 690                traceId: context.TraceId,
 691                policyVersion: context.PolicyVersion,
 692                policyHash: context.PolicyHash),
 3493            DlpFailureBehavior.WarnAndAllow => GovernanceDecision.Warning(
 3494                reason,
 3495                correlationId: context.CorrelationId,
 3496                traceId: context.TraceId,
 3497                policyVersion: context.PolicyVersion,
 3498                policyHash: context.PolicyHash),
 499            DlpFailureBehavior.Deny => GovernanceDecision.Deny(
 4100                reason,
 4101                correlationId: context.CorrelationId,
 4102                traceId: context.TraceId,
 4103                policyVersion: context.PolicyVersion,
 4104                policyHash: context.PolicyHash),
 6105            DlpFailureBehavior.Defer => GovernanceDecision.Defer(
 6106                reason.Code,
 6107                reason.Message,
 6108                correlationId: context.CorrelationId,
 6109                traceId: context.TraceId,
 6110                policyVersion: context.PolicyVersion,
 6111                policyHash: context.PolicyHash),
 4112            DlpFailureBehavior.RequireAcknowledgment => GovernanceDecision.RequireAcknowledgment(
 4113                reason.Code,
 4114                reason.Message,
 4115                correlationId: context.CorrelationId,
 4116                traceId: context.TraceId,
 4117                policyVersion: context.PolicyVersion,
 4118                policyHash: context.PolicyHash),
 6119            DlpFailureBehavior.Escalate => GovernanceDecision.Escalate(
 6120                reason.Code,
 6121                reason.Message,
 6122                correlationId: context.CorrelationId,
 6123                traceId: context.TraceId,
 6124                policyVersion: context.PolicyVersion,
 6125                policyHash: context.PolicyHash),
 0126            _ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "DLP failure behavior must be defined
 60127        };
 128
 60129        return new DlpFailurePolicyResolution(context, behavior, reason, decision);
 130    }
 131
 132    private static string BuildMessage(DlpFailurePolicyContext context)
 133    {
 60134        return context.FailureKind switch
 60135        {
 34136            DlpClassificationFailureKind.ServiceUnavailable => "DLP/classification screening service was unavailable.",
 8137            DlpClassificationFailureKind.Timeout => context.Timeout.HasValue
 8138                ? $"DLP/classification screening timed out after {context.Timeout.Value.TotalMilliseconds:0} ms."
 8139                : "DLP/classification screening timed out.",
 6140            DlpClassificationFailureKind.IndeterminateResult => "DLP/classification screening returned an indeterminate 
 6141            DlpClassificationFailureKind.BlockedResult => "DLP/classification screening returned a blocked result.",
 6142            DlpClassificationFailureKind.ClassifiedResult => "DLP/classification screening returned a classified result 
 0143            _ => throw new ArgumentOutOfRangeException(nameof(context), context.FailureKind, "DLP failure kind must be d
 60144        };
 145    }
 146
 147    private static IReadOnlyDictionary<string, string> BuildReasonMetadata(
 148        DlpFailurePolicyContext context,
 149        DlpFailureBehavior behavior)
 150    {
 60151        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 152
 132153        foreach (KeyValuePair<string, string> item in context.Metadata)
 154        {
 6155            if (string.IsNullOrWhiteSpace(item.Key))
 156            {
 157                continue;
 158            }
 159
 6160            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 161        }
 162
 60163        metadata["dlp.failure_kind"] = ToMetadataValue(context.FailureKind);
 60164        metadata["dlp.risk_level"] = ToMetadataValue(context.RiskLevel);
 60165        metadata["dlp.behavior"] = ToMetadataValue(behavior);
 166
 60167        if (!string.IsNullOrWhiteSpace(context.IntentCategory))
 168        {
 4169            metadata["dlp.intent_category"] = context.IntentCategory;
 170        }
 171
 60172        if (!string.IsNullOrWhiteSpace(context.Environment))
 173        {
 4174            metadata["dlp.environment"] = context.Environment;
 175        }
 176
 60177        if (context.Timeout.HasValue)
 178        {
 2179            metadata["dlp.timeout_ms"] = context.Timeout.Value.TotalMilliseconds
 2180                .ToString("0", CultureInfo.InvariantCulture);
 181        }
 182
 60183        return metadata.Count == 0
 60184            ? EmptyMetadata
 60185            : new ReadOnlyDictionary<string, string>(metadata);
 186    }
 187
 188    private static string ToMetadataValue<TEnum>(TEnum value)
 189        where TEnum : struct, Enum
 190    {
 180191        string text = value.ToString();
 192
 180193        return string.Concat(
 180194            text.Select((character, index) =>
 1956195                index > 0 && char.IsUpper(character)
 1956196                    ? "_" + char.ToLowerInvariant(character)
 1956197                    : char.ToLowerInvariant(character).ToString()));
 198    }
 199}