Skip to content

Repository files navigation

semconv.com

A version-by-version record of the OpenTelemetry semantic conventions, specification and OTLP. Diffed at every release, committed, and rendered as a site plus a small JSON API and MCP server meant to be read by agents as much as by people.

Why

opentelemetry.io/docs/specs answers "what does the spec say today" and nothing else. The questions that actually come up building a backend are different:

  • What changed between the version I read and today?
  • Is db.statement still a thing, and what replaced it?
  • Which MUSTs appeared in a stable document since we shipped?
  • Did a field number move on the wire?

Three sources, three methods

Source Read as
semantic-conventions Its machine-readable registry — every attribute carries a stability level and, when deprecated, the name of its successor.
opentelemetry-specification Its RFC 2119 requirements. Each MUST/SHOULD/MAY is extracted and hashed, so a new requirement is distinguishable from a reflowed paragraph. Requirements that move between sections are matched across.
opentelemetry-proto The .proto files themselves — messages, field numbers, types, cardinality, enum values, retired numbers — plus the protocol prose.
semantic-conventions-genai The same definition/2 registry model, in its own repository. Untagged — see below.

The OTLP protocol specification is not in the specification repository. specification/protocol/otlp.md is a stub redirecting to the website; the real 800-line document ships in docs/specification.md of opentelemetry-proto. Anything auditing an OTLP server needs that file, and it is easy to miss.

Layout

Path What it is
src/ingest/ Effect CLI: fetch upstream tags, normalize, write data/
src/model/types.ts The normalized snapshot shape
src/model/change.ts The change vocabulary all three differs share
src/model/diff*.ts The diff engines — pure, and the most heavily tested part
src/model/catalog.ts Build-time aggregation: lifecycles, renames, consecutive diffs
site/ Astro site (srcDir), prerendered to dist/
data/ Committed, gzipped per-version snapshots

Commands

bun install
bun run ingest        # fetch any new upstream releases into data/
bun run test          # diff engine, asserted against real releases
bun run typecheck
bun run dev           # site at http://localhost:4321
bun run build

bun run ingest --force re-normalizes every tracked release. It should produce a byte-identical data/ — snapshots are fully sorted and gzipped at a fixed level precisely so that "the file changed" is a reliable signal that upstream moved.

The GenAI registry is untagged

v1.44.0 split the gen_ai.* namespace into its own repository, which has never cut a release: no tags, a towncrier CHANGELOG reading only "Unreleased", and stability: development on the registry itself. Waiting for a tag would mean tracking nothing while the attributes are already in production use.

So it is tracked from main, versioned by commit date (2026-09-03) with the short SHA kept as the tag, and labelled unreleased everywhere it appears. Snapshots are written only when the normalized model changes, not when the branch moves — otherwise data/ would grow by the calendar.

The consequence worth knowing: ~59 attributes exist in both registries — deprecated "Moved to..." stubs in semantic-conventions, live definitions in GenAI. Anything answering "is this key still current" has to consult both, or it reports live attributes as dead. check_attribute_names returns status moved for these.

Two model formats

The conventions are mid-migration between definition/1 (one flat groups: list) and definition/2 (typed top-level sections, key: instead of id:, ref_group: instead of extends:). Both are read. This is not a detail: v1.44.0 moved server.*, client.*, source.*, destination.* and every hw.* metric to the newer format, and a format-1-only reader reports all of them as deleted from the registry.

Keeping the data current

.github/workflows/sync.yml runs daily. It ingests any new upstream releases, and when data/ actually changes it pushes sync/upstream and files an issue with a compare link. Upstream ships roughly monthly, so on most days it finds nothing and exits silently.

Tests run against the new snapshots before the issue is filed, and the issue says whether they passed. A failure there means upstream changed a model, a document layout or the proto grammar in a way a normalizer does not yet understand — merging would publish wrong history.

Deploys stay manual (bun run deploy), so nothing reaches the live site without someone looking at the diff first.

For agents

/llms.txt maps the machine-readable surface. /api/*.json is generated at build time and served straight off the CDN. /mcp is a public, read-only MCP server (streamable HTTP, no auth, no session state) whose tools read those same asset files through the Worker's ASSETS binding — so the MCP answers and the pages cannot drift apart.

The tool that earns its keep is check_attribute_names: give it the attribute keys a codebase emits and it reports which are deprecated, renamed, or absent from the registry.

Severity

breaking means: for the conventions, removing or renaming something already marked stable or release_candidate; for the specification, adding, dropping or restrengthening a requirement in a document marked Stable; for OTLP, changing an existing field in a released package. The same change on a development definition is notable. Wording, examples and guidance are informational.

Both OTLP encodings are load-bearing, which is why nearly any change to an existing field counts: binary keys on the field number, JSON keys on the field name. A rename breaks every JSON client while the binary format never notices.

Upstream release notes use their own categories and the two do not always agree; both are shown on each release page.

Deploying

One Worker in front of prerendered assets: /mcp is handled by src/mcp/worker.ts, everything else is served from dist/, and the MCP tools read those same asset files through the ASSETS binding — so the API and the pages cannot drift.

bun run deploy   # build, then wrangler deploy

wrangler.jsonc declares semconv.com and www.semconv.com as custom domains, which requires the zone to be on Cloudflare with this account's nameservers.

Deployed headers live in public/_headers, not in the endpoint code. Cloudflare serves dist/ through its asset layer and discards the headers an Astro endpoint sets on its Response; those only apply under astro preview. CORS on /api/* comes from that file.

The same asset-first routing means the Worker's www → apex redirect only fires for paths with no matching asset (/mcp, 404s). Asset paths are served on www too, with every page's <link rel="canonical"> pointing at the apex. To make it a hard 301 everywhere, add a zone-level Redirect Rule in the Cloudflare dashboard (Rules → Redirect Rules): match http.host eq "www.semconv.com", redirect to concat("https://semconv.com", http.request.uri), 301, preserve query string. That runs at the edge for free — the alternative, assets.run_worker_first, bills a Worker invocation for every asset request.

Data

Snapshots start at semconv v1.30.0, specification v1.42.0 and OTLP v1.4.0. Earlier releases used schemas and layouts different enough that diffing across them would report changes the projects never made.

Licence

Apache-2.0. See LICENSE and NOTICE.

The contents of data/ are derived from the OpenTelemetry semantic-conventions, specification and proto repositories, © The OpenTelemetry Authors, also Apache-2.0.

This is an independent project and is not affiliated with, endorsed by, or sponsored by the OpenTelemetry project, the CNCF, or the Linux Foundation.

About

A version-by-version record of the OpenTelemetry semantic conventions, specification and OTLP — what was renamed, what became required, what changed on the wire.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages