Description
Found by the SKT-0012 validation run against the EKS lab cluster, 2026-08-27.
internal/capture/k8s.go:172 sets the captured cluster name from clusterNameFromContext(<current kubectl context>) (:186), which strips an EKS ARN to its final segment and otherwise slugifies whatever the context happens to be called. The same cluster therefore yields a different identity depending on which kubeconfig entry the operator used: an ARN context gives the real EKS cluster name, a tailscale or hostname context gives a slug of the API server hostname.
Neither is what the telemetry carries. The collector Helm release sets its own cluster name, readable in-cluster from the k8s-monitoring release-info ConfigMap in the monitoring namespace, and that is the value stamped on every metric, log and trace the cluster actually ships. On the lab cluster all three values differ.
Why it is severe rather than cosmetic: cluster name is the primary join key across every k8s construct. A blueprint forged with the wrong one produces synthetic telemetry that can never join to the real cluster dashboards, which defeats the entire purpose of capturing from that cluster. The failure is also silent — the blueprint loads and validates, and the data looks correct until someone tries to overlay it on the real thing.
The fix is a source precedence with the observable truth first, and a record of which source was used so the operator can see it rather than having to infer it. Do not silently fall back: a capture whose cluster identity came from the kubeconfig should say so.
Acceptance Criteria
- #1 The captured cluster name prefers the identity the collector actually stamps on telemetry, where that is discoverable in-cluster
- #2 An EKS ARN context remains a correct fallback when the collector identity is unavailable
- #3 The capture records which source the cluster name came from, visibly to the operator
- #4 A capture that fell back to the kubeconfig context name says so rather than presenting the value as authoritative
- #5 Verified against the lab cluster: the captured name matches the cluster label on that cluster real telemetry
Definition of Done
- #1 make gate (build vet test race rw-proto-check spdx-check forbidden-words)
- #2 make blueprint-schema (only if a blueprint field or construct/workload config struct changed)
- #3 DRY_RUN=true go run ./cmd/synthkit -once -dump — inventory diffed against signals/
Implementation Notes
FIXED 2026-08-27 (lane L10), each with a failing test first and a live before/after against the EKS lab cluster.
resolveClusterName (internal/capture/k8s.go:187) replaces the bare clusterNameFromContext call. Precedence, each branch recording its source: collector release-info ConfigMap -> EKS ARN -> slugified kubeconfig context -> a labelled default.
The collector value is used VERBATIM, never slugified, because it has to be byte-identical to the cluster label on real telemetry. clusterNameFromEKSARN now requires an actual arn:…:cluster/… prefix rather than blindly splitting on the last / or : — the old form is what turned a tailscale hostname into a plausible-looking cluster name.
Probe targets are derived from the collector workload names already captured and capped at 8, and it is a targeted get of a NAMED ConfigMap reading one key — never a namespace- or cluster-wide list, which would pull every ConfigMap data through the process and defeat the zero-secret default.
Cluster.NameSource is on the wire, skcapture prints the name and its source on completion, and anything other than collector-release-info prints a WARNING telling the operator to verify against a live signal before forging a blueprint.
LIVE: before, the captured name was derived from the tailscale API-server hostname. After, it is the collector value, and AC #5 was proven by reading the rendered Alloy config in-cluster — cluster = the captured value, byte-identical. The old value shared no characters with it. Both fallback paths were also exercised live (k3d gives kubeconfig-context + warning; a stub kubectl serving an ARN context gives eks-arn-context + warning).
IN-CLUSTER PATH STILL OPEN: the shipped RBAC grants no ConfigMap access, so an in-cluster Job falls back to the context name. Tracked as SKT-0012.09 — it is a security-posture decision, not a lane one.
Final Summary
Cluster identity now comes from the cluster. resolveClusterName prefers the collector release-info ConfigMap, then an EKS ARN, then the slugified kubeconfig context, then a labelled default — and records which source it used, warning on anything but the first.
The collector value is used VERBATIM, never slugified, because it must be byte-identical to the cluster label on real telemetry. Verified live by reading the rendered Alloy config in-cluster: the captured name and the stamped cluster label match exactly, where the previous kubeconfig-derived value shared no characters with it. Both fallback paths were exercised live too.
The lookup stays inside the trust boundary: a targeted get of a NAMED ConfigMap reading one key, never a namespace-wide list, which would pull every ConfigMap data through the process and defeat the zero-secret default.
The in-cluster path is NOT closed here and is tracked as SKT-0012.09: the shipped RBAC deliberately grants no ConfigMap access, so a Job falls back to the context name — correctly, and it now says so, but that is the degraded path and every capture taken the documented way gets the wrong name until the grant is decided.