# 097 — Metrics have no gauge/counter distinction, so `rate()`/`sum()`/`avg()` can be applied without a semantics guard ## Summary `MetricPrimitiveKind` only distinguishes data *type* (`String`/`Number`/`Timestamp`/`Duration`/`Boolean`), not metric *semantics* (counter vs. gauge). PromQL's `gauge()` op is a bespoke query-time "take the last point" function rather than something tied to how a metric was declared, and nothing stops calling `rate()` on a metric that's conceptually a gauge, or `sum()`/`avg()` on a monotonically-increasing counter without rating it first. ## Where - `src/engine/data_value.rs:70-76` — `MetricPrimitiveKind`: `String, Number, Timestamp, Duration, Boolean`. No `Counter`/`Gauge` variant. - `src/engine/metric_primitive.rs:8-14` — `MetricPrimitive` mirrors the same type-only distinction (`String, Number(Number), Timestamp, Duration(i64), Boolean`). - `src/engine/promql.rs:9-16` — `AggOp` includes `Gauge` as one of six query-time aggregation ops (`Sum, Avg, Count, Rate, Histogram, Gauge`), evaluated in `src/engine/snapshot/mod.rs:993-1001` by taking only `points.last()` per series — a query-side convenience, not a property of the declared metric. - The only type guard on aggregation today is `src/engine/snapshot/mod.rs:888-896`, which rejects `sum`/`avg` on `MetricPrimitiveKind::String` — there's no equivalent guard for applying `rate()` to a non-monotonic value or `sum()`/`avg()` to an un-rated counter. ## Scope - Add a semantics field to metric declarations (e.g. extend `MetricPrimitiveKind` with `Counter`/`Gauge` variants, or add a separate `MetricSemantics` enum alongside the existing type field — decide which based on whether type and semantics should ever vary independently for the same declaration). - Thread that through `DeclareMetricRequest`/`MetricDeclarationRecord`/ `MetricDeclarationDto` (the same three-places pattern noted in this repo's `CLAUDE.md` for declaration features: `engine.rs` message + handler, `settings.rs`/`profile.rs` persistence, `api.rs` route, plus a webapp UI affordance in `Params.tsx`/`Metrics`'s declaration form). - Use it to validate PromQL queries at evaluation time (e.g. warn or reject `rate()` on a declared gauge, or `sum()`/`avg()` directly on a declared counter without an inner `rate()`), and consider whether the current ad hoc `gauge()` PromQL wrapper becomes redundant once the declaration itself carries this information (a bare selector on a declared gauge could just work the way `gauge()` does today).