ReasonMetadata — Structured Diagnostic Metadata (v1.40.0)

Every Reason-derived type now carries a Metadata property of type ReasonMetadata — a lightweight sealed record capturing who created this error. It is completely separate from Tags (no framework leakage into your business metadata).

// ReasonMetadata is captured automatically — no extra code needed
public sealed record ReasonMetadata
{
    public string? CallerMember { get; init; }  // name of the method that created the error
    public string? CallerFile   { get; init; }  // source file path
    public int?    CallerLine   { get; init; }  // line number
    public static readonly ReasonMetadata Empty = new();
}

// Accessing metadata
var error = ValidationError.Field("Email", "Invalid format");
Console.WriteLine(error.Metadata.CallerMember);  // name of the calling method — e.g. "ValidateUser"

// Reading from an IReason-typed reference via IReasonMetadata capability interface
IReason reason = error;
ReasonMetadata? meta = reason.TryGetMetadata();     // null if IReason doesn't implement IReasonMetadata
bool hasCaller = reason.HasCallerInfo();            // true when CallerMember is set

Three data planes — clean separation:

Plane Storage Purpose
Message string Human-readable description for logs and UI
Tags ImmutableDictionary<string, object> Business/domain context (HttpStatusCode, FieldName, …)
Metadata ReasonMetadata System/diagnostic context (who created this error)

JSON serialization"metadata" is written only when Metadata != Empty; reading is backward-compatible (missing key → Empty):

{
  "type": "ValidationError",
  "message": "Invalid format",
  "tags": { "ErrorType": "Validation", "FieldName": "Email" },
  "metadata": { "CallerMember": "ValidateEmail", "CallerFile": "/src/UserService.cs", "CallerLine": 42 }
}