> ## Content Index
> Fetch the complete content index at: https://devinside.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# One Telemetry Contract Across a .NET AI Platform
- URL: https://devinside.cloud/one-telemetry-contract-across-a-net-ai-platform/
- Published: 2026-03-10T12:15:00.000Z
- Updated: 2026-11-12T17:08:28.000Z
- Description: A shared dashboard can make a platform look unified long before its measurements are comparable. One service records request duration in seconds. Another records milliseconds. A third starts the timer after retrieval, while the first includes it.
- Author: Marcin Borecki
- Tags: Engineering, Data & Observability, #m20, #devinside-review

START WITH THE MECHANISM

Interactive companion and laboratory update: 12 November 2026\. The editorial date places the topic on the timeline; this current explanation and its measured evidence retain their actual preparation and execution dates.

THE FULL INVESTIGATION

## One Telemetry Contract Across a .NET AI Platform

Editorial context: 10 March 2026 · Prepared and updated: 12 November 2026

Dates and technology context

This is a retrospective editorial timeline. Topic context: 10 March 2026\. First publicly available: 12 November 2026\. Current text, visuals and laboratory revisions were prepared on 12 November 2026.

The C# normalization companion and TypeScript conformance checks ran on 12 November2026\. Node24.22.1 is an October2026 release and must remain in the later update, not March2026.

A shared dashboard can make a platform look unified long before its measurements are comparable. One service records request duration in seconds. Another records milliseconds. A third starts the timer after retrieval, while the first includes it. All three publish a metric with an encouragingly similar name.

The problem is not primarily a tooling problem. It is a disagreement about meaning. Until the teams agree on boundaries, units, identity and outcomes, a central observability platform collects incompatible stories in one place.

An AI platform makes this harder because one user operation can include retrieval, multiple model calls, tool calls, retries and a final validation step. Counting model calls as user requests makes activity look larger. Counting a fluent response as success can hide a failed tool. We need a telemetry contract that defines what the system is observing before we decide how to display it.

## Begin with questions that cross services

Write the operational questions first. Which user operations are getting slower? Is the delay in retrieval, a model endpoint, a tool or a queue? Are more operations failing, or are the same operations making more attempts? Can a particular release explain the change?

Each question implies a boundary and a denominator. A business operation count should increment once for the operation, not once for every internal model call. A tool-attempt counter should include retries if it is intended to measure dependency pressure. A terminal failure counter should not count an intermediate error that was recovered successfully.

This is why a naming convention alone is insufficient. Two teams can use the same name and still measure different things. The contract must specify the start, stop, unit, permitted attributes and error classification. It should also identify the owner who can explain and change those semantics.

Avoid beginning with every possible dashboard. A short set of cross-service questions provides a smaller, more testable contract. New observations can be added when a decision needs them, rather than because an instrumentation library exposes another convenient switch.

## Separate resource identity from operation identity

01 / See the idea

### Describe the emitter and the operation

![Resource attributes and operation attributes have different roles in telemetry.](https://devinside.cloud/content/images/2026/11/devinside/M20-v1.svg)

[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M20-v1.svg)

A hypothetical service release emits an answer operation that later fails validation. Service version describes the emitter; outcome describes the operation and should not be embedded in a span name.

Interpretation and evidence
- Resource identifies the producer.
- Operation describes the work.
- Stable names preserve comparison.
conceptual/hypothetical, not measured

Some information describes the process producing telemetry: service name, deployment environment, release version and instance. Other information describes the operation being observed: operation type, outcome, stage or attempt category.

Keeping those roles distinct makes aggregation easier to reason about. A service version should not be embedded in every span name. A request identifier should not become part of a metric's identity. A tenant identifier may be necessary for a restricted investigation but inappropriate as an unrestricted metric dimension.

Define a stable vocabulary. For example, `document.analyze` can be the business operation type across services, while `retrieval.search` and `tool.execute` describe internal stages. The names are application-owned examples, not a claim of compliance with a particular generation of AI semantic conventions.

Changing the vocabulary is a schema change. If `completed` means “model answered” in one release and “answer passed validation” in another, the dashboard will show a discontinuity that may look like a reliability improvement. The migration needs a versioned definition and, sometimes, parallel measurements during transition.

## Write a contract that can be reviewed

A useful telemetry specification can be plain data. It should be understandable by the engineers who emit it and the people who make decisions from it.

```yaml
# Illustrative application contract, not collector configuration.
schema: example.ai.operations.v1
metric:
  name: example.ai.operation.duration
  instrument: histogram
  unit: s
  start: server accepts responsibility for the operation
  stop: operation reaches a terminal application state
  allowed_attributes:
    operation.kind: [answer, summarize, classify]
    outcome: [succeeded, failed, cancelled]
  prohibited_attributes:
    - request.id
    - user.email
    - prompt.text
    - document.name

```

This example is intentionally small. It does not cover every kind of long-running operation, and it should not conceal outcomes that remain uncertain. If uncertainty is a legitimate state, define a separate pending or reconciliation measurement rather than forcing it into success or failure.

The contract should state how timeouts are classified and whether a cancellation after acceptance remains an application responsibility. It should also define when measurement occurs for operations that survive a process restart. A duration contract that cannot survive the system's actual lifecycle is not ready for production.

## Keep metric dimensions finite on purpose

Metric attributes create separate time series. Their combinations matter. In a hypothetical design, three operation types, three outcomes and four regions already permit 36 combinations before instances, versions or model deployments are considered. This is arithmetic, not a measured backend limit.

Adding a request identifier changes the nature of the measurement. The system no longer aggregates many similar operations into a small set of series. It creates an effectively new series for every request. That can increase memory, processing and storage costs while making the dashboard harder to use.

Use bounded categories that correspond to operational decisions. If the team needs to investigate one operation, link from an aggregate view to its trace or structured record. Do not force metrics to serve as an event database.

Microsoft's metrics guidance explains the instrument model and the use of tags in .NET. The application must still control the values it supplies. An API that accepts arbitrary tag strings does not make arbitrary tag cardinality a good design. [Creating metrics in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-instrumentation?ref=devinside.cloud).

## Make the easy code the correct code

02 / A concrete example

### A small vocabulary has a visible bound

![Hypothetical categories multiply to 36 possible combinations before other dimensions.](https://devinside.cloud/content/images/2026/11/devinside/M20-v2.svg)

[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M20-v2.svg)

Three operation types, three outcomes and four regions permit 36 combinations in this toy schema. Adding a distinct request ID changes the bound with traffic instead of the reviewed vocabulary.

Interpretation and evidence
- Count combinations, not just tag names.
- Request IDs belong in correlated records.
- The 36 is arithmetic, not a backend limit.
conceptual/hypothetical, not measured

A shared library can encode the contract, but it should be small enough that teams can understand its behavior. Wrap application measurements, not every feature of the telemetry SDK. The goal is to prevent semantic drift, not to build a second observability platform.

The following .NET 10 fragment uses BCL metrics. It is illustrative and has not been compiled or executed for this manuscript.

```csharp
using System.Diagnostics;
using System.Diagnostics.Metrics;

public static class OperationMetrics
{
    private static readonly Meter Meter =
        new("Example.AI.Operations", "1.0.0");

    private static readonly Histogram<double> Duration =
        Meter.CreateHistogram<double>(
            "example.ai.operation.duration", "s");

    public static void Record(
        double seconds, OperationKind kind, OperationOutcome outcome)
    {
        var tags = new TagList
        {
            { "operation.kind", kind.ToString().ToLowerInvariant() },
            { "outcome", outcome.ToString().ToLowerInvariant() }
        };
        Duration.Record(seconds, tags);
    }
}

```

`OperationKind` and `OperationOutcome` are application enums whose permitted values must match the contract. The wrapper does not accept a free-form dictionary from the request. That constraint is intentional. Additional dimensions should require a reviewed change rather than appearing because one caller had a useful-looking string.

The wrapper also does not choose the timing boundary. The caller must start and stop the operation at the defined points. A common library cannot compensate for a business process that has no clear terminal state.

## Propagation is a contract between owners

Tracing breaks when context disappears at a queue, scheduled job or custom transport. It also becomes confusing when every component starts a new root span without explaining its relationship to earlier work.

Define propagation for each boundary. An incoming HTTP request may create an execution context. A producer passes context with a message. A consumer records an attempt associated with the message and the durable operation. Batch consumers may need links to multiple contributing contexts rather than a fictional single parent.

W3C Trace Context provides a standardized representation of trace context. It does not define the business meaning of the operation or authorize access to it. Treat propagated values as correlation data and validate them through the appropriate tracing APIs. [W3C Trace Context](https://www.w3.org/TR/2021/REC-trace-context-1-20211123/?ref=devinside.cloud).

Do not place secrets or unrestricted business data in baggage simply because it propagates conveniently. Propagation expands the audience for a value across services. That is a data-governance decision as well as a debugging convenience.

## Define AI outcomes at the application boundary

03 / Watch the mechanism

### Transport success is not task success

![A model response passes transport but fails evidence validation before the operation outcome.](https://devinside.cloud/content/images/2026/11/devinside/M20-v3.svg)

Transport success is not task successA model response passes transport but fails evidence validation before the operation outcome.M20-V3 / TECHNICAL EXPLANATIONTransport success is not task successConceptual / hypothetical example. Not measured results.Model calltext returnedQuality gatecitation missingOperationrejected answerInvocation success ≠ accepted task outcome

Play animation[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M20-v3.svg)

Static view. Play to see how the mechanism unfolds.

A hypothetical model call returns text successfully, but the text lacks a required citation. Count a successful invocation and a rejected application answer separately.

Interpretation and evidence
- Observe the model call.
- Observe the quality gate.
- Count terminal operations independently.
conceptual/hypothetical, not measured

A model returning text is a transport success, not necessarily a successful user outcome. The text may fail a required schema, omit mandatory evidence or propose an unauthorized action. The telemetry contract should distinguish those cases.

Use separate observations for model invocation, tool invocation and final application outcome. A model timeout is a dependency failure. A tool denial can be the correct enforcement of policy. An answer rejected for missing evidence is a quality outcome. Combining them into one generic error counter makes both reliability and quality harder to improve.

Similarly, separate token usage from useful completion. A run that consumes fewer tokens but fails to answer the question may not be cheaper in any meaningful sense if the user repeats the task. Cost per successful operation is often a more useful decision boundary, provided the definition of success is independent and explicit.

Avoid presenting estimated usage as exact billing. Provider responses, application estimates and invoice reconciliation are different evidence sources. Label them accordingly. The contract should make it possible to compare like with like without implying that every number has the same authority.

## Test the contract as data

Create a small in-memory collection harness that observes emitted measurements and activities. Trigger a successful operation, a failed dependency, a denied action and a cancellation. Assert names, units, required attributes and the absence of prohibited fields.

Test cardinality deliberately. Send many distinct request identifiers through the application and verify that they do not appear as metric labels. Then vary only the permitted categories and check that the resulting attribute combinations remain bounded by the declared vocabulary.

For traces, verify parent or link relationships across a controlled asynchronous boundary. Do not merely assert that some spans exist. An orphaned span is evidence that instrumentation ran, but it may be useless for answering the cross-service question that motivated the work.

Also test an unavailable collector. Collection failure should follow a documented buffering and drop policy, and the service should not create an unbounded queue of diagnostic objects. The instrumentation walkthrough makes clear that activity creation and collection are separate layers; each needs a failure model. [Distributed tracing instrumentation in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs?ref=devinside.cloud).

## Standardization has a cost too

A platform team can over-standardize. If every application-specific question requires a central committee, engineers will work around the contract or stop instrumenting useful behavior. A common schema should cover shared meanings while allowing clearly namespaced local measurements.

The distinction is between incompatible duplication and legitimate specialization. Every service should agree on what a terminal operation means. A document extractor and a recommendation service do not need identical internal stages. Their local measurements can differ while their shared outcomes remain comparable.

Version the shared contract and document compatibility rules. Additive attributes may still increase cardinality. Renaming an outcome may break alerts. Changing a histogram boundary can affect downstream interpretation. Treat these changes as consumer changes, not harmless refactoring inside a logging library.

Assign ownership to dashboards and alerts as well as emitters. If no one owns the interpretation, the contract will decay even when the code remains consistent. A metric's consumer is part of its design.

## Make the platform explainable across team boundaries

The practical acceptance test is a conversation between teams. Can one team explain another service's latency graph without asking where its timer starts? Can an incident commander distinguish a tool denial from an application failure? Can a release comparison identify a schema change rather than misreading it as a performance change?

If those questions have clear answers, a shared telemetry contract is doing useful work. It reduces the amount of translation required during an incident and makes aggregate evidence more trustworthy.

The goal is not to make every service emit the same data. It is to make shared observations mean the same thing. Once meaning is stable, dashboards become a useful view of the platform. Before that, they are only a visually consistent collection of disagreements.

## Turn a shared name into an executable boundary

The smallest useful implementation of a telemetry contract is not a dashboard. It is a boundary that can reject a record whose meaning is ambiguous. The executable companion added here takes one deliberately narrow case: normalizing a duration from a versioned JSON document into seconds.

This companion was built and executed on 12 November 2026 with SDK 9.0.203 and runtime 9.0.4\. It does not claim that the run happened at the historical context date. It also does not replace the broader .NET AI platform described above with a three-field example. Its purpose is to show how one semantic rule becomes real code, a correctness check and a bounded allocation experiment.

The accepted document has exactly three fields: `schema`, `unit` and `value`. Its schema identifier is the application-owned `di.duration.v1`. The allowed units are `s` and `ms`; the value must be finite and nonnegative. Unknown fields, repeated fields, an unknown schema and a string masquerading as a numeric value are rejected. This strictness is a deliberate contract choice, not a universal recommendation that all JSON APIs must reject extension fields.

For example, these two records have the same normalized result:

```json
{"schema":"di.duration.v1","unit":"ms","value":1250}

```

```json
{"value":1.25,"unit":"s","schema":"di.duration.v1"}

```

Both represent 1.25 seconds. A record with `unit: "ticks"` does not become comparable by guessing a conversion. A record containing two `value` properties does not become trustworthy by silently selecting the last one. The parser needs to know whether it has received one valid statement before the platform starts aggregating it with other statements.

The policy also exposes an important design decision about schema evolution. If the producer begins adding fields, this consumer fails explicitly. A more permissive system might ignore extensions, but that must be part of the version's documented behavior. The companion chooses the stricter rule because an apparently harmless extra dimension can change the meaning of an observation. Another version can choose a different rule without pretending the contract never changed.

## Two parsers, one normalization rule

The laboratory implements a straightforward document-based reader and a streaming reader. Both must satisfy the same independent expected values and rejection cases. The comparison would be meaningless if the streaming version gained speed by skipping validation performed by the document version.

The shared final rule is small:

```csharp
private static double Finish(int mask, string unit, double value)
{
    if (mask != 7 || !double.IsFinite(value) || value < 0)
        throw new FormatException("Invalid duration contract");

    return unit switch
    {
        "s" => value,
        "ms" => value / 1000,
        _ => throw new FormatException("Unknown unit")
    };
}

```

The mask records whether each required field has appeared. Each field receives a separate bit. Seeing an already-set bit rejects a duplicate; reaching the end without all three bits rejects an incomplete record. This makes field order irrelevant without making field presence or duplication invisible.

The document version parses the input into a `JsonDocument`, enumerates its properties, checks their names and values, and calls the shared rule. It disposes the document when processing ends. This is readable code and a reasonable baseline. The experiment does not assume that a document model is a mistake simply because a streaming alternative exists.

The streaming version uses `Utf8JsonReader` and compares expected property and string values without asking for a new managed string for every token. The main distinction is between reading a token and materializing another representation of that token. [Microsoft's ValueTextEquals documentation](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.utf8jsonreader.valuetextequals?view=net-9.0&ref=devinside.cloud) defines the comparison against the unescaped JSON token value.

The central field-selection step is:

```csharp
int bit = reader.ValueTextEquals("schema") ? 1
    : reader.ValueTextEquals("unit") ? 2
    : reader.ValueTextEquals("value") ? 4
    : throw new FormatException("Unknown property");

if ((mask & bit) != 0)
    throw new FormatException("Duplicate property");

mask |= bit;

```

This fragment is used only after verifying that the current token is a property name. The complete implementation also verifies object boundaries, string versus number token kinds, the expected schema, supported unit values and the absence of trailing JSON. Those checks are in the downloadable source. Omitting them from the implementation merely because the abbreviated example is easier to read would change what was measured.

Neither parser authenticates the producer or proves that the reported duration corresponds to a real operation. The contract establishes a structural and unit-level meaning for this one record. A producer can still report a perfectly valid false number. Authorization, provenance and the operation's start and stop semantics remain separate responsibilities of the platform.

## Correctness before an allocation claim

The companion executes the same cases through both parsers. Three accepted fixtures cover equivalent seconds and milliseconds, reordered fields and zero duration. Nine rejected fixtures cover an empty object, an array root, an unknown schema, an unknown unit, a negative value, a repeated value, an unexpected field, a string value and trailing JSON. That produces 24 explicit checks, all of which passed in the executed run.

These are not an exhaustive proof over every possible JSON document. They are targeted checks for the declared contract. The important independence is that acceptance and normalized results are chosen by the fixture, rather than by treating one implementation as a universal oracle for the other. Agreement between two parsers that both accepted duplicate fields would not vindicate either parser under this contract.

The malformed-input cases also have a boundary. Exception allocation on failure is not included in the successful-path measurements reported below. A hostile or frequently malformed input distribution could create a different performance problem. That would require an experiment that includes failures and their handling policy, not a reinterpretation of the success-only table.

Likewise, these inputs are small and already stored as UTF-8 byte arrays. Receiving bytes from a network, constructing the arrays, decompressing a body and retaining documents for later use are outside the measured interval. A complete telemetry ingestion service must account for those costs elsewhere.

## The measured cost of the narrow successful path

The executed comparison alternates the document and streaming implementations across six samples each. Every sample processes 30,000 records drawn repeatedly from the three valid fixtures and checks that the accumulated normalized value is exactly 25,000\. Input creation happens before the timed region. A small warm-up precedes each sample, and the run uses the runtime's ordinary configuration rather than a specially disabled optimization mode.

| Implementation | Samples | Median sample-average time per record | Range of sample averages   | Median managed bytes per record |
| -------------- | ------- | ------------------------------------- | -------------------------- | ------------------------------- |
| JsonDocument   | 6       | 2.4989 microseconds                   | 1.4448–2.7291 microseconds | 258.67 B                        |
| Utf8JsonReader | 6       | 1.6343 microseconds                   | 0.9319–1.9567 microseconds | 0 B reported                    |

![Managed allocation in the successful telemetry-normalization path](https://devinside.cloud/content/images/2026/11/devinside/M20-measured-normalization.svg)

The allocation result supports a specific claim: the streaming implementation did not produce measured current-thread managed allocation inside this successful parsing-and-normalization loop. The document implementation did. It does not support a claim that the telemetry platform is allocation-free. The prebuilt input arrays, process startup, output report, failed-input path and any downstream storage are outside that statement.

The timing result is exploratory. The ranges show substantial variation even within this short single-process run. Tiering, warm-up and other work on the shared development machine can affect the observations. The reported median is a summary of six sample averages, not a latency percentile of individual records and not a confidence interval. The code did not establish a production throughput ceiling.

The allocation counter itself is also a contract. `GC.GetAllocatedBytesForCurrentThread` observes managed allocation associated with the current thread; it does not report native allocation or the total size of objects surviving collection. This companion uses a synchronous loop, so it does not accidentally treat allocation after an asynchronous thread switch as though it still belonged to the original thread. [Microsoft's allocation-counter documentation](https://learn.microsoft.com/en-us/dotnet/api/system.gc.getallocatedbytesforcurrentthread?view=net-9.0&ref=devinside.cloud) describes that boundary.

The useful engineering choice is therefore conditional. If parsing this compact record is a hot path and a streaming implementation remains maintainable, avoiding repeated token materialization can be worthwhile. If the system needs to retain and inspect a flexible document tree, the document model may be doing necessary work. If network or storage dominates, the local parsing difference may have little effect on the operation the user experiences.

## A rejection is sometimes the most useful observation

Suppose one service emits seconds and another emits milliseconds but both use the same metric name. A graphing system that adds the raw values produces a plausible chart with no coherent unit. Our companion rejects unknown units and converts supported units before aggregation. That prevents one class of semantic disagreement, provided the producers are truthful about which unit they used.

Now suppose a producer changes the stop event from “model returned text” to “answer passed validation.” No numeric conversion repairs that difference. Both values can be measured in seconds and remain structurally valid while describing different operations. The schema and operational contract must distinguish them. The normalizer's narrow success is a reminder of the broader contract, not an excuse to stop defining it.

A similar limit applies to failures. The current record does not carry a terminal-outcome field, an operation identifier or an attempt count. It cannot decide whether a timeout belongs in a success histogram. Adding such fields would create another versioned schema and another set of independent expected cases. The companion deliberately does not infer missing semantics from a duration alone.

![Validation stages in the duration contract](https://devinside.cloud/content/images/2026/11/devinside/M20-contract-boundaries.svg)

This is the practical meaning of a shared telemetry contract. Each accepted observation should carry enough defined meaning for the intended comparison, and each rejected observation should have a reason the producer can act on. Silently accepting a record can be more damaging than losing one point, because it may make the whole aggregate look comparable when it is not.

## Reproduce, extend and preserve the meaning

[Download the complete normalization companion and raw samples](https://devinside.cloud/content/files/2026/11/M20-lab.zip). It contains the two parsers, explicit input fixtures, checksum verification and the actual measurement file. From the lab directory, run `dotnet run -c Release -- check` before using `measure`. The pinned SDK and runtime are historical reproduction inputs, not a recommendation to install old patches on a production system.

A useful next experiment would add a realistic distribution of malformed records and measure a chosen rejection strategy. Another could compare complete ingestion paths while preserving the same normalized outputs. The TypeScript conformance addition below uses the same selected input cases and expected values. It is a correctness check, not a cross-language speed comparison. No malformed-input performance result is implied by the current run.

Most importantly, keep a change to the contract separate from a change to its implementation. Replacing the document parser with a streaming parser should preserve accepted records and normalized values. Introducing a new unit, changing duplicate-field behavior or redefining the stop event changes meaning and must be reviewed as such. A fast parser that accepts the wrong story is still wrong.

## A second implementation tests the meaning, not the language speed

The downloadable companion now also contains `normalize.ts` and a shared, inspectable `fixtures.json`. The TypeScript implementation was executed on Node 24.22.1 on 12 November 2026, with all 12 selected fixtures passing. This is a contemporary addition; the Node version is not represented as available at the older historical context date. The C# implementations passed the corresponding accepted and rejected cases independently.

The point is to check a semantic boundary across implementations. Both accept the two equivalent representations of 1.25 seconds and the zero-duration record. Both reject the selected malformed shapes, unknown schema or unit, negative duration, duplicate value, unexpected field, string number and trailing data. This selected corpus is not an exhaustive equivalence proof for all possible JSON text.

A subtle implementation choice is important here. Calling `JSON.parse` on the complete object and only then checking its properties loses duplicate-key information. The TypeScript companion instead reads a deliberately restricted object grammar and checks each decoded key before inserting it into a map. It allows primitive values only, because nested documents are outside this duration-record contract. It is educational contract code, not a replacement for a general-purpose JSON parser or an internet-facing ingestion security boundary.

The lexical step may decode individual string and numeric tokens with `JSON.parse`, but object construction remains under the contract's control. A repeated key therefore reaches an explicit rejection rather than silently replacing a previous value. The distinction illustrates why a cross-language fixture should include invalid cases, not just a few happy-path examples whose outputs happen to match.

Run the TypeScript companion with the recorded Node version using `node normalize.ts`. Its output reports the fixture count and runtime. There is no TypeScript performance table here, and the C# timing results above should not be treated as a comparison against JavaScript. The independently useful result is that the selected semantic contract survives a second implementation, including its refusal behavior.

Historical versions and verification boundaries

These pins existed before the context date. Availability and static API checks do not establish a successful build or a production-safe patch level. Execution claims are limited to explicitly identified companion checks.

| Component                     | Version  | Available by | Source                                                                                                                   |
| ----------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| .NET SDK                      | 10.0.100 | 2025-11-11   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/10.0/releases.json?ref=devinside.cloud) |
| .NET runtime                  | 10.0.0   | 2025-11-11   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/10.0/releases.json?ref=devinside.cloud) |
| ASP.NET Core shared framework | 10.0.0   | 2025-11-11   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/10.0/releases.json?ref=devinside.cloud) |
| C#                            | 12.0     | 2024-12-03   | [Primary record](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12?ref=devinside.cloud)                |
| OpenTelemetry                 | 1.9.0    | 2024-06-14   | [Primary record](https://www.nuget.org/packages/OpenTelemetry/1.9.0?ref=devinside.cloud)                                 |