Description
The unbounded-cardinality half of GCI-0018, commissioned separately because it needs no new source contract. The identity-bearing half stays parked on the parent.
The input already exists
collector/sources/dataplane.py cardinality() calls Mimir /api/prom/api/v1/cardinality/label_names per stack and returns top_labels: a list of {label, values} where label is a label NAME and values is a count. No label value is fetched, and that file’s own comment records the privacy decision - offender names go to Loki and S3 and never become a metric label.
collector/pillars/cost.py cost_cardinality_outliers consumes only top_labels[0] as Worst label and discards the rest. The remaining names are already in the scan envelope and are thrown away at view time.
Wave 1 parked the parent on a finding that no key-only label-name payload existed. That finding was wrong; it checked only collector/sources/signal_inventory.py. See the parent’s correction note.
What this task must NOT claim
The payload is Mimir only, and it is the top N by cardinality rather than a complete label-name set. So:
- Do not represent this as covering Loki, Tempo or Pyroscope. The view’s signal column is
metricsfor every row and the dashboard says so. - Do not represent it as a complete label-name sweep. It is the top N per stack by cardinality, and the view must state N beside the rows so an operator can see the window rather than reading absence as a clean result.
- Do not extend it to the identity-bearing class. Top-N-by-cardinality is a poor PII input: a low-cardinality identity label such as an owner slug never enters the window, so a PII view built on it would be silently incomplete. That is the parent’s scope and it waits for the all-signal contract.
Absolute rules, inherited from the parent
- Match on the label KEY only. Never read, store, log, emit or sample a label VALUE for this purpose.
- Never emit a label name as a metric label. Label names are unbounded. The metric is a per-stack count plus a bounded class enum; the names go to a
views/table and Loki, exactly as metric names already do. - Publish a confidence tier.
trace_idandpod_template_hashare near-certain;endpointandpathare frequently legitimate. Report high-confidence and possible separately and never merge them into one number an operator cannot defend. - Report only, never remediate. The platform must not propose dropping a label and must not touch an Adaptive Metrics rule.
- The pattern set is a versioned data file with a test, like the technology registry, carrying nothing derived from a specific estate.
- Findings carry a measured-stack denominator. A stack whose cardinality read failed is unreadable, never a clean zero.
Where it lands
Pillar E, risk and hygiene, beside the existing public-dashboard and access-policy findings.
New output:
views/risk_label_cardinality- stack, label name, value count, class, confidence tier, the signal (alwaysmetricsfor now), and the top-N window size.- Bounded metrics: findings per confidence tier plus stacks measured as the denominator. No label name and no stack-by-label cross product in Mimir. Every metric declared in
collector/emit/budget.pyCATALOGUE. - A risk dashboard row leading with the high-confidence count and the measured-stack denominator beside it.
The pattern set
Generic unbounded-cardinality label-name patterns, none derived from any one estate:
uuid, guid, id
trace_id, traceid, span_id, spanid, request_id, requestid, correlation_id
run_id, job_id, build_id, task_id, container_id, pod_template_hash, pid
timestamp, epoch, time, datetime
url, path, full_path, query, querystring, endpoint, route, uri
Classify endpoint, route and path as possible rather than high-confidence: a parameterised route label is legitimate and common.
Acceptance Criteria
- #1 Classification matches label keys only; no label value is read, stored, logged or emitted
- #2 Label names never become metric labels; they go to views/risk_label_cardinality and Loki
- #3 High-confidence and possible findings are reported separately and never merged
- #4 The pattern set is a versioned data file with a test and contains nothing estate-specific
- #5 Findings carry a measured-stack denominator; a failed cardinality read is unreadable, never zero
- #6 The view and the dashboard state the top-N window and that coverage is Mimir only
- #7 Every emitted metric is declared in budget.py CATALOGUE and tests/test_budget.py passes
- #8 The platform reports only and never proposes or performs remediation
Definition of Done
- #1 just test
- #2 just tf-validate
- #3 just check-identifiers and just no-em-dashes both return clean
Implementation Plan
Lane 1 implements the versioned generic pattern data, pure key-only classifier, risk-pillar view composition, and focused tests within its frozen ownership. Root then wires the bounded catalogue metrics, hydration ownership and dependencies, Loki publication, scan envelope, and dashboard row; an independent security lane reviews the integrated privacy boundary before the final gate.
Final Summary
Delivered at a8c745bab901fbd0c4ae85202b791ccd70ef6557. The collector classifies only the existing Mimir top-20 label-name payload with a versioned generic pattern set, publishes the measured view and separate bounded high-confidence and possible counts, forwards identity-bearing detail only in S3 and Loki bodies, and adds a Mimir-only risk dashboard row with its measured-stack denominator. No source read or remediation path was added. Independent security review found no privacy or cardinality issue. CodeRabbit reviewed all changed files; its valid dashboard-isolation and budget-record findings were fixed, while its remaining mutable-test-fixture style suggestion was left because the fixture is never mutated and this repository has no Ruff gate. Exact-SHA just check: 1480 passed, 2 existing skips, 7273 subtests; peak RSS 232.7 MiB; both Terraform roots valid; customer identifiers clean.