feat(telemetry): shared metrics vocabulary + operator reconcile emitter - #183
Merged
Conversation
…rpc to 1.82.1 Resolve goconst lint on the reconcile guard test by asserting against the package label constants instead of string literals. Bump the pre-existing indirect google.golang.org/grpc dependency 1.82.0 -> 1.82.1 to clear the High-severity GHSA-hrxh-6v49-42gf flagged by the Grype scan.
…allback registration Addresses PR review findings on the metrics vocabulary and reconcile emitter packages: - RegisterBuildInfo now validates nil meter / empty component and sets an explicit unit, matching reconcile.New's contract; adds real test coverage via a ManualReader (was previously untested). - RegisterManagedResources rejects a second registration on the same Emitter instead of silently double-counting gauge observations. - Fixes the ownership guard test's gateway-ban assertion, which checked an unreachable substring and never actually enforced the RFC's D3 ban. - Documents the RecordReconcileError extra-attributes contract and the reconcile triplet's namespace/name cardinality trade-off. - Updates README.md/CLAUDE.md package tables and doc.go for the telemetry/metrics rename and the new telemetry/reconcile package. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…failure sync.Once permanently latched RegisterManagedResources on its first call regardless of outcome, so a transient RegisterCallback failure (e.g. a mismatched meter) locked the Emitter into "already registered" forever. Guard with a mutex instead and only latch on success.
glageju
marked this pull request as ready for review
July 23, 2026 00:40
reyortiz3
reviewed
Jul 23, 2026
reyortiz3
reviewed
Jul 23, 2026
Address review: toolhive-core should not enumerate the apps that consume it. Remove the per-component Component* value roster from metrics/ownership; each component now supplies its own stacklok.component value. Keep the AttrStacklok* keys and the frozen ProductStacklokPlatform, which is a single cross-cutting value every emitter must share verbatim. Remove telemetry/reconcile entirely. It is operator-specific vocabulary shared by two operators (ai-gateway, toolhive-enterprise); its placement (shared-operator library vs core) is an open RFC/D12 question to settle separately, not in this foundational vocabulary PR.
reyortiz3
reviewed
Jul 23, 2026
reyortiz3
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every Stacklok component today invents its own metric vocabulary — one calls the upstream server
mcp_server, anotherserver; one histogram is in seconds, the next in milliseconds; success-vs-failure is a label here and two separate metric names there. The result: you can't write a single cross-component dashboard or alert without memorizing five schemes, and a rename in one repo silently breaks a join in another.This is the foundational PR of the Metrics Standardization RFC (D6/D8). It puts the shared vocabulary in
toolhive-core— one place that defines the label spellings, the histogram bucket shapes, and the emitter-ownership attributes — so every downstream component spells the same concept the same way. Nothing here emits a metric; it's vocabulary plus construction helpers that everyone imports.What it adds
One package:
telemetry/metrics.LabelMCPServer,LabelOutcome,LabelMCPMethod, …) and 3 outcome values (success/error/rejected), so outcome rides on a label instead of splitting into_succeed/_failedmetric names.BucketsFastHTTP(),BucketsMCPProxy(),BucketsLongRunning()— returned as fresh slices so callers can't alias a shared one.stacklok.component/stacklok.productresource-attribute keys, plus the frozen product valuestacklok-platform. The per-componentstacklok.componentvalue is supplied by each component — core names the concept, not the roster of apps that consume it.RegisterBuildInfo(...)+ the fleet-widestacklok.build_infogauge for build/release correlation.How downstream uses it
A proxy or gateway builds its own instrument with a shared preset and label key — no meter comes from this package:
Ownership attributes go on the OTel resource once at startup; the component value is the caller's own:
Design decisions worth a reviewer's eye
Meter(), no metric-name constants. The caller owns its meter and passes a preset's boundaries; this package registers no global state and pins no instrument names.ProductStacklokPlatform: every component must emit that identical string or the fleet-widestacklok_productselector breaks, so it's a single cross-cutting contract rather than a per-app value.stacklok.productfrozen atstacklok-platform. Deliberately notstacklok-enterprise— the value lands in customer dashboards and must survive the enterprise→platform rename without churn.Tests
The package is guarded by pin tests that fail loudly on a silent respelling — the whole point of the shared vocabulary is that these strings don't drift. They pin every bucket boundary set, label key, outcome value, ownership key, and the frozen product value; plus
build_infoname/unit andRegisterBuildInfobehavior (validation + the empty→unknownfallback, scraped from a real in-memory OTel reader).Also in this PR
google.golang.org/grpcbumped1.82.0 → 1.82.1to clear a High-severity advisory (GHSA-hrxh-6v49-42gf) the Grype scan flagged — a pre-existing indirect dep, not introduced here, but it rides into the release tag downstream will pin.Not in this PR
The operator reconcile emitter (D12) was split out on review. It's operator-shared vocabulary, not universal, so its placement — raw
toolhive-corevs a shared-operator library — is an open D12 question tracked separately rather than settled here.Merge order
This must merge and cut a release tag first.
toolhive,toolhive-registry-server, andstacklok-enterprise-platformall import this vocabulary and can't build standalone until they bump theirtoolhive-corepin to the release carrying it.Links