< Summary

Information
Class: AsiBackbone.Core.Metadata.DefaultGovernanceMetadataSanitizer
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Metadata/DefaultGovernanceMetadataSanitizer.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 139
Line coverage: 100%
Branch coverage
94%
Covered branches: 32
Total branches: 34
Branch coverage: 94.1%
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%88100%
SanitizeAsync()95.45%2222100%
GetStricterAction(...)75%44100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Metadata/DefaultGovernanceMetadataSanitizer.cs

#LineLine coverage
 1using AsiBackbone.Core.Results;
 2
 3namespace AsiBackbone.Core.Metadata;
 4
 5/// <summary>
 6/// Provides the default provider-neutral governance metadata classification, sanitation, and budget-validation pipeline
 7/// </summary>
 8/// <remarks>
 9/// The pipeline normalizes caller-owned metadata into a new collection, applies classifiers in registration order,
 10/// removes or redacts values as directed, and then applies <see cref="GovernanceMetadataBudgetValidator" /> to the
 11/// sanitized collection. Classifier exceptions are not converted into permissive results; they stop the pipeline so
 12/// hosts do not accidentally persist or emit metadata that was not successfully classified.
 13/// </remarks>
 14public sealed class DefaultGovernanceMetadataSanitizer : IGovernanceMetadataSanitizer
 15{
 216    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 217        GovernanceMetadataBudgetValidator.Normalize(null);
 18
 19    private readonly IReadOnlyList<IGovernanceMetadataClassifier> classifiers;
 20    private readonly GovernanceMetadataBudget budget;
 21
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="DefaultGovernanceMetadataSanitizer" /> class.
 24    /// </summary>
 25    /// <param name="classifiers">The host-owned classifiers applied in registration order.</param>
 26    /// <param name="budget">The metadata budget applied after classification and sanitation.</param>
 1027    public DefaultGovernanceMetadataSanitizer(
 1028        IEnumerable<IGovernanceMetadataClassifier>? classifiers = null,
 1029        GovernanceMetadataBudget? budget = null)
 30    {
 1031        IGovernanceMetadataClassifier[] resolvedClassifiers = classifiers?.ToArray() ?? [];
 32
 3033        foreach (IGovernanceMetadataClassifier classifier in resolvedClassifiers)
 34        {
 535            ArgumentNullException.ThrowIfNull(classifier);
 36        }
 37
 1038        this.classifiers = resolvedClassifiers;
 1039        this.budget = budget ?? GovernanceMetadataBudget.Recommended;
 1040    }
 41
 42    /// <inheritdoc />
 43    public async ValueTask<GovernanceMetadataSanitizationResult> SanitizeAsync(
 44        IReadOnlyDictionary<string, string>? metadata,
 45        CancellationToken cancellationToken = default)
 46    {
 847        cancellationToken.ThrowIfCancellationRequested();
 48
 849        IReadOnlyDictionary<string, string> normalizedMetadata =
 850            GovernanceMetadataBudgetValidator.Normalize(metadata);
 851        Dictionary<string, string> sanitizedMetadata = new(normalizedMetadata.Count, StringComparer.Ordinal);
 852        List<OperationReason> reasons = [];
 853        GovernanceMetadataSanitizationAction overallAction = GovernanceMetadataSanitizationAction.Allow;
 54
 3855        foreach (KeyValuePair<string, string> item in normalizedMetadata)
 56        {
 1157            cancellationToken.ThrowIfCancellationRequested();
 58
 1159            GovernanceMetadataSanitizationAction entryAction = GovernanceMetadataSanitizationAction.Allow;
 1160            string replacementValue = item.Value;
 1161            var context = new GovernanceMetadataClassificationContext(
 1162                item.Key,
 1163                item.Value,
 1164                normalizedMetadata);
 65
 4066            foreach (IGovernanceMetadataClassifier classifier in classifiers)
 67            {
 968                GovernanceMetadataClassificationResult classification = await classifier
 969                    .ClassifyAsync(context, cancellationToken)
 970                    .ConfigureAwait(false);
 71
 972                ArgumentNullException.ThrowIfNull(classification);
 73
 974                entryAction = GetStricterAction(entryAction, classification.Action);
 75
 976                if (classification.Reason is not null)
 77                {
 978                    reasons.Add(classification.Reason);
 79                }
 80
 981                if (classification.Action is GovernanceMetadataSanitizationAction.Redact)
 82                {
 283                    replacementValue = classification.ReplacementValue
 284                        ?? GovernanceMetadataClassificationResult.DefaultRedactedValue;
 85                }
 86            }
 87
 1188            overallAction = GetStricterAction(overallAction, entryAction);
 89
 1190            if (entryAction is GovernanceMetadataSanitizationAction.Allow
 1191                or GovernanceMetadataSanitizationAction.Warn)
 92            {
 293                sanitizedMetadata[item.Key] = item.Value;
 94            }
 995            else if (entryAction is GovernanceMetadataSanitizationAction.Redact)
 96            {
 297                sanitizedMetadata[item.Key] = replacementValue;
 98            }
 1199        }
 100
 8101        GovernanceMetadataBudgetValidationResult budgetValidation =
 8102            GovernanceMetadataBudgetValidator.Validate(sanitizedMetadata, budget);
 103
 8104        if (!budgetValidation.IsValid)
 105        {
 2106            overallAction = GovernanceMetadataSanitizationAction.Deny;
 2107            reasons.Add(OperationReason.Create(
 2108                GovernanceMetadataSanitizationReasonCodes.BudgetViolation,
 2109                "Governance metadata failed the configured shape or reserved-key budget."));
 110        }
 111
 8112        IReadOnlyDictionary<string, string> forwardMetadata =
 8113            overallAction is GovernanceMetadataSanitizationAction.Deny
 8114                ? EmptyMetadata
 8115                : budgetValidation.NormalizedMetadata;
 116
 8117        IReadOnlyList<OperationReason> resultReasons = reasons.Count == 0
 8118            ? []
 8119            : reasons.AsReadOnly();
 120
 8121        return GovernanceMetadataSanitizationResult.Create(
 8122            overallAction,
 8123            forwardMetadata,
 8124            resultReasons,
 8125            budgetValidation);
 8126    }
 127
 128    private static GovernanceMetadataSanitizationAction GetStricterAction(
 129        GovernanceMetadataSanitizationAction current,
 130        GovernanceMetadataSanitizationAction candidate)
 131    {
 20132        return !Enum.IsDefined(candidate)
 20133            ? throw new ArgumentOutOfRangeException(
 20134                nameof(candidate),
 20135                candidate,
 20136                "Metadata sanitation action must be defined.")
 20137            : candidate > current ? candidate : current;
 138    }
 139}