FAILURE MAP
← Case archive

FA-10276 / Observability / Open access

Metric series identity: Counter and gauge identities collide · case 01

Counter and gauge identities collide.

Verified by executionVariant 1 · 6 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The implementation substitutes unit, None for unit, kind, so counter and gauge identities collide.

VERIFIED REPAIR

Include the instrument kind in identity.

Unsuccessful approach: The attempted repair substitutes unit, unit. Fixture 1 still yields ('requests', (('a', '2'), ('z', '1')), 'count', 'count') instead of ('requests', (('a', '2'), ('z', '1')), 'count', 'counter').

Case contract

A series identity includes metric name, sorted label key-value pairs excluding configured dropped keys, measurement unit, and instrument kind. Dropping values never drops other equal-valued labels.

Why this case matters

An offline model of metric series identity, suitable for testing build and release tooling without external services.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(name, labels, dropped, unit, kind):
    return (name, tuple(sorted((k,v) for k,v in labels.items() if k not in dropped)), unit, None)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('requests', {'z': '1', 'a': '2'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 2', solve('requests', {'a': '2', 'z': '1'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 3', solve('requests', {'a': '2', 'z': '1'}, ['z'], 'count', 'counter'), ('requests', (('a', '2'),), 'count', 'counter'))
check('fixture 4', solve('requests', {'a': 'same', 'b': 'same'}, ['a'], 'count', 'counter'), ('requests', (('b', 'same'),), 'count', 'counter'))
check('fixture 5', solve('latency', {}, [], 'seconds', 'histogram'), ('latency', (), 'seconds', 'histogram'))
check('fixture 6', solve('latency', {}, [], 'milliseconds', 'gauge'), ('latency', (), 'milliseconds', 'gauge'))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
fixture 1['requests', [['a', '2'], ['z', '1']], 'count', None]['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Failed
fixture 2['requests', [['a', '2'], ['z', '1']], 'count', None]['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Failed
fixture 3['requests', [['a', '2']], 'count', None]['requests', [['a', '2']], 'count', 'counter']Failed
fixture 4['requests', [['b', 'same']], 'count', None]['requests', [['b', 'same']], 'count', 'counter']Failed
fixture 5['latency', [], 'seconds', None]['latency', [], 'seconds', 'histogram']Failed
fixture 6['latency', [], 'milliseconds', None]['latency', [], 'milliseconds', 'gauge']Failed

SHA-256 / 634599d736d276b888632539c80cf29f0b5361f8e8d69baaa6c6798735f5163e

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(name, labels, dropped, unit, kind):
    return (name, tuple(sorted((k,v) for k,v in labels.items() if k not in dropped)), unit, unit)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('requests', {'z': '1', 'a': '2'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 2', solve('requests', {'a': '2', 'z': '1'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 3', solve('requests', {'a': '2', 'z': '1'}, ['z'], 'count', 'counter'), ('requests', (('a', '2'),), 'count', 'counter'))
check('fixture 4', solve('requests', {'a': 'same', 'b': 'same'}, ['a'], 'count', 'counter'), ('requests', (('b', 'same'),), 'count', 'counter'))
check('fixture 5', solve('latency', {}, [], 'seconds', 'histogram'), ('latency', (), 'seconds', 'histogram'))
check('fixture 6', solve('latency', {}, [], 'milliseconds', 'gauge'), ('latency', (), 'milliseconds', 'gauge'))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
fixture 1['requests', [['a', '2'], ['z', '1']], 'count', 'count']['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Failed
fixture 2['requests', [['a', '2'], ['z', '1']], 'count', 'count']['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Failed
fixture 3['requests', [['a', '2']], 'count', 'count']['requests', [['a', '2']], 'count', 'counter']Failed
fixture 4['requests', [['b', 'same']], 'count', 'count']['requests', [['b', 'same']], 'count', 'counter']Failed
fixture 5['latency', [], 'seconds', 'seconds']['latency', [], 'seconds', 'histogram']Failed
fixture 6['latency', [], 'milliseconds', 'milliseconds']['latency', [], 'milliseconds', 'gauge']Failed

SHA-256 / 5fbae1a343ef42e7c8a0dcdfaca221146ef11289e606b6f721b5f5f09a78f12a

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(name, labels, dropped, unit, kind):
    return (name, tuple(sorted((k,v) for k,v in labels.items() if k not in dropped)), unit, kind)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('requests', {'z': '1', 'a': '2'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 2', solve('requests', {'a': '2', 'z': '1'}, [], 'count', 'counter'), ('requests', (('a', '2'), ('z', '1')), 'count', 'counter'))
check('fixture 3', solve('requests', {'a': '2', 'z': '1'}, ['z'], 'count', 'counter'), ('requests', (('a', '2'),), 'count', 'counter'))
check('fixture 4', solve('requests', {'a': 'same', 'b': 'same'}, ['a'], 'count', 'counter'), ('requests', (('b', 'same'),), 'count', 'counter'))
check('fixture 5', solve('latency', {}, [], 'seconds', 'histogram'), ('latency', (), 'seconds', 'histogram'))
check('fixture 6', solve('latency', {}, [], 'milliseconds', 'gauge'), ('latency', (), 'milliseconds', 'gauge'))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
fixture 1['requests', [['a', '2'], ['z', '1']], 'count', 'counter']['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Passed
fixture 2['requests', [['a', '2'], ['z', '1']], 'count', 'counter']['requests', [['a', '2'], ['z', '1']], 'count', 'counter']Passed
fixture 3['requests', [['a', '2']], 'count', 'counter']['requests', [['a', '2']], 'count', 'counter']Passed
fixture 4['requests', [['b', 'same']], 'count', 'counter']['requests', [['b', 'same']], 'count', 'counter']Passed
fixture 5['latency', [], 'seconds', 'histogram']['latency', [], 'seconds', 'histogram']Passed
fixture 6['latency', [], 'milliseconds', 'gauge']['latency', [], 'milliseconds', 'gauge']Passed

SHA-256 / 9e2ad6d4ea502456262bee663f6abc3f165cc50c3dc77f1f1d028f1f461a694b

Verification & scope

This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:38:37.493318+00:00.

Case digest / 7a304957defa75b8d88482b1f28d5e3ccf81642da07b42ae5a5e88bc8f6ae983