◆ NFRGate / Rule Reference

M3 — Cardinality is bounded

high 📊 metrics · both

New metric labels/tags are drawn from a bounded, enumerable set - no raw user ID, email, full URL with path params, or similar unbounded value used as a label.

Python Implementation

M3: new metric labels/tags are drawn from a bounded, enumerable set — no
raw user ID, email, full URL, or similar unbounded value used as a label.
rubric_store/definitions/metrics.yaml tags this `both`; the static half
checks the *names* of keyword arguments passed to a `.labels(...)`/
`.tags(...)` call against a blocklist of inherently high-cardinality field
names. It cannot verify the actual *value* is bounded (a variable named
`status` could still hold something unbounded) — that's the LLM half's job.

Only fires on diffs that actually add a `.labels(...)`/`.tags(...)` call;
a diff with no new metric labels at all produces no M3 finding, matching
the rubric's own "N/A" treatment for that case (rubric_store/
validation_notes.md's PAY-482 example: "pass (N/A) — No new metric labels
introduced.") rather than fabricating one.

Java Implementation

M3 for Java: new metric tags are drawn from a bounded, enumerable set.
Java's Micrometer idiom (`.tag("key", value)` / `.tags(...)`) passes the tag
key as a positional string literal — there are no keyword-argument names to
check the way the Python version does, so this checks the literal key
strings themselves. See ast_helpers.has_unbounded_metric_tag.

Only fires on diffs that add a `.tag(...)`/`.tags(...)` call, matching the
Python version's "no new labels at all -> no finding" (the rubric's own
N/A-as-pass treatment), not a fabricated pass/fail.

Go Implementation

M3 for Go: new metric labels are drawn from a bounded, enumerable set.
Go's prometheus client_golang idiom (`.With(prometheus.Labels{"key":
value})`) has no keyword-argument names to check — this looks textually
across the whole call's argument text for an unbounded-looking field name.
See ast_helpers.has_unbounded_metric_label.

Only fires on diffs that add a `.With(...)` call, matching the Python/Java
versions' "no new labels at all -> no finding."
← All rules