Skip to content

[finding] MetricSchema.filters is an authorable per-metric filter with zero consumers — a hand-authored cube's filters: [{ sql }] is parsed and dropped #10414

Description

@os-warren

Noticed while implementing #10298 (PR #10411). Recorded, not acted on — deciding it is ADR-0049 enforce-or-remove work, not this card's.

The declaration

packages/spec/src/data/analytics.zod.ts, MetricSchema:

/** Filtering for this specific metric (e.g. "Revenue from Premium Users") */
filters: z.array(strictObject(
  { surface: 'this metric filter',
    history: 'Until #4001 batch D an undeclared key beside `sql` was silently dropped.' },
  { sql: z.string() },
)).optional(),

It is a real authoring surface: defineCube() parses an author literal and defineStack({ analyticsCubes }) carries every cube through StackSchema.parse, so an author writing filters on a metric gets a clean parse. packages/spec/src/data/analytics.test.ts pins that the key survives the parse (expect(metric.filters).toHaveLength(1)).

Nothing reads it

Measured on origin/main at 801296050:

$ git grep -n "\.filters" -- 'packages/services/service-analytics/src/**' 'packages/drivers/**' \
    | grep -v '\.test\.ts'
(no output)

Zero is a finding here only because the same search shape finds a neighbouring metric key that IS consumed — the positive control:

$ git grep -c "\.format" -- 'packages/services/service-analytics/src/**'
packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts:1
packages/services/service-analytics/src/__tests__/dataset-dimension-field-descriptors.test.ts:2
packages/services/service-analytics/src/__tests__/query-dataset.test.ts:1
packages/services/service-analytics/src/analytics-service.ts:1
packages/services/service-analytics/src/dataset-compiler.ts:1

format is read; filters is read nowhere outside the spec's own parse test. NativeSQLStrategy.resolveMeasureSql wraps measure.sql in the aggregate for measure.type and never looks at measure.filters; ObjectQLStrategy.resolveMeasureAggregation does the same.

Why this is the #10298 shape, one level up

#10298 was a dataset measure whose filter was declared and dropped, producing a 200 with different arithmetic than the author wrote. This is the identical failure for a hand-authored cube: filters: [{ sql: "stage = 'closed_won'" }] parses, registers, and returns the unfiltered aggregate under the author's metric name. The dataset path is now repaired (#10411) through a separate channel — the compiled dataset's own registry entry — so MetricSchema.filters remains inert with the dataset half fixed around it.

The decision this needs

filters[].sql is a raw SQL string, which is why #10411 did not adopt it as the carrier and reached for a structured FilterCondition sidecar instead. Enforcing it would mean answering:

  • Does the platform want a raw-SQL authoring surface on a metric at all? Every other filter surface in the tree is a structured FilterCondition that the strategies lower and parameterize; a raw fragment cannot be bound, cannot be re-targeted per driver dialect, and cannot be inspected by the lint rules that walk filters (packages/lint/src/filter-walk.ts enumerates "widget filters, list-view filters, dataset and measure filters" — not this one).
  • If not, ADR-0049's remove leg applies, and the retirement route is the spec-property-retirement playbook.
  • If yes, the consumer has to be written, and the injection surface argued.

Either way it is a packages/spec decision, which #10298's lane holds as a zero-ownership boundary.

Related: #10298 · PR #10411 · #10413.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions