Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/backend/Shared/Telemetry/KbxUxTelemetryContracts.cs
T

45 lines
2.5 KiB
C#

using System.Text.Json;
using Kbx.Shared.Telemetry.Generated;
using Kbx.Shared.Experiments.Generated;
namespace Kbx.Shared.Telemetry;
public sealed record KbxUxEventRequest(
string EventName,
string? ScreenId,
string? ScreenVersion,
Guid SessionId,
Guid? TaskSessionId,
string? AppVersion,
string? ExperimentId,
string? ExperimentVariant,
DateTimeOffset OccurredAt,
int? DurationMs,
int? Count,
IReadOnlyDictionary<string,string>? Attributes);
public sealed record KbxUxEventBatchRequest(IReadOnlyList<KbxUxEventRequest> Events);
public sealed record KbxUxMetricRow(string MetricKey,string Label,double? Value,string Unit,long SampleCount,double? P50=null,double? P95=null);
public sealed record KbxUxMetricsResponse(DateOnly From,DateOnly To,string? ScreenId,IReadOnlyList<KbxUxMetricRow> Metrics);
public static class KbxUxTelemetryGuard
{
private static readonly string[] ForbiddenFragments = ["phone","address","name","orderno","customer","itemcode","barcode","keyword","query","email","businessnumber","entityid"];
public static void Validate(KbxUxEventRequest item)
{
if (!KbxTelemetryCatalog.Events.TryGetValue(item.EventName,out var definition)) throw new ArgumentException($"Unknown telemetry event: {item.EventName}");
if (definition.RequiresDuration && item.DurationMs is null) throw new ArgumentException($"{item.EventName} requires durationMs.");
if (item.DurationMs is < 0 or > 86_400_000) throw new ArgumentOutOfRangeException(nameof(item.DurationMs));
if (item.ExperimentId is { Length: > 160 } || item.ExperimentVariant is { Length: > 80 }) throw new ArgumentException("Experiment context is too long.");
if ((item.ExperimentId is null) != (item.ExperimentVariant is null)) throw new ArgumentException("Experiment id and variant must be supplied together.");
if (item.Count is < 1 or > 1_000_000) throw new ArgumentOutOfRangeException(nameof(item.Count));
var allowed=definition.AllowedAttributes.ToHashSet(StringComparer.Ordinal);
foreach(var pair in item.Attributes ?? new Dictionary<string,string>())
{
if(!allowed.Contains(pair.Key)) throw new ArgumentException($"Attribute '{pair.Key}' is not allowed for {item.EventName}.");
if(ForbiddenFragments.Any(x=>pair.Key.Contains(x,StringComparison.OrdinalIgnoreCase))) throw new ArgumentException($"Attribute '{pair.Key}' is forbidden for telemetry.");
if(pair.Value.Length>80) throw new ArgumentException($"Attribute value is too long: {pair.Key}");
}
}
}