Skip to content

Commit ac70996

Browse files
committed
fix(spec): the analytics_cube sql ledger notes say what the schema admits, and the schema comment names the ObjectQL refusal's real partition
The dimensions.sql note said '*' is refused at parse; the schema admits it on a dimension, as on any measure, and the measures.sql note no longer limits it to a count. The count-only boundary is #21000's. The schema comment now says the ObjectQL path refuses only the number / string / boolean partition. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent e6b66ec commit ac70996

2 files changed

Lines changed: 6 additions & 3 deletions

File tree

‎packages/spec/liveness/analytics_cube.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"verifiedAt": "2026-09-30",
6464
"evidence": "packages/services/service-analytics/src/strategies/native-sql-strategy.ts#resolveMeasureSql — `measure.sql` is the column the aggregate is applied to (`'*'` the COUNT(*) form), and `qualifyAndRegisterJoin(measure.sql, …)` is what lowers a dotted reference into a LEFT JOIN chain; packages/services/service-analytics/src/strategies/objectql-strategy.ts#resolveFieldName reads `measure.sql.replace(/^\\$/, '')` as the aggregate field for the `engine.aggregate` path; packages/services/service-analytics/src/analytics-service.ts#fieldsOfColumnSql resolves it to the field(s) the analytics door's field-level read gate judges.",
6565
"producer": "packages/cli/src/commands/serve.ts#CAPABILITY_PROVIDERS — the `analytics` entry declares `configKey: 'analyticsCubes'` and the capability resolver threads it into the plugin (`const cubes = (config as any).analyticsCubes ?? (config as any).cubes ?? []; arg = { cubes }`); packages/services/service-analytics/src/analytics-service.ts#registerAll (`if (config.cubes) this.cubeRegistry.registerAll(config.cubes)`) is where the authored array becomes the registry every consumer below resolves through. Without this thread an authored cube reaches no reader at all — the `seed.env` shape (#4837).",
66-
"note": "REQUIRED. NARROWED 2026-09-30 (#20943, maintainer ruling D; ADR-0021 zero raw expressions, ADR-0049 enforce-or-remove): the value is a COLUMN REFERENCE — a bare identifier, a dotted identifier path (relationship hops, then the column), or `'*'` on a `count` measure — and any SQL expression is refused at parse with a prescription naming the ADR-0021 dataset form (a measure-scoped `filter`, `derived: { op, of }`). The admitted pattern is the one `IDENTIFIER_PATH` (native-sql-strategy.ts) and the read gate already use to tell a column path from an expression (#4157), so every admitted value resolves to a field the gate can judge. Still LIVE: the key itself is unchanged and read at the three sites above. The runtime's expression branches (verbatim emit on the raw-SQL path, the gate's stand-down) remain for a cube that reaches the service without meeting the parse; their deletion is the services-lane follow-up. The D3 entry is `cube-member-sql-expression-retired`; there is no D2 conversion, because an expression has no mechanical rewrite into a dataset."
66+
"note": "REQUIRED. NARROWED 2026-09-30 (#20943, maintainer ruling D; ADR-0021 zero raw expressions, ADR-0049 enforce-or-remove): the value is a COLUMN REFERENCE — a bare identifier, a dotted identifier path (relationship hops, then the column), or `'*'`, which the schema admits on any measure (the count-only boundary is #21000's) — and any SQL expression is refused at parse with a prescription naming the ADR-0021 dataset form (a measure-scoped `filter`, `derived: { op, of }`). The admitted pattern is the one `IDENTIFIER_PATH` (native-sql-strategy.ts) and the read gate already use to tell a column path from an expression (#4157), so every admitted value other than `'*'` (which reads no field value) resolves to a field the gate can judge. Still LIVE: the key itself is unchanged and read at the three sites above. The runtime's expression branches (verbatim emit on the raw-SQL path, the gate's stand-down) remain for a cube that reaches the service without meeting the parse; their deletion is the services-lane follow-up. The D3 entry is `cube-member-sql-expression-retired`; there is no D2 conversion, because an expression has no mechanical rewrite into a dataset."
6767
},
6868
"format": {
6969
"status": "live",
@@ -107,7 +107,7 @@
107107
"verifiedAt": "2026-09-30",
108108
"evidence": "packages/services/service-analytics/src/strategies/native-sql-strategy.ts#resolveDimensionSql — `dim.sql` is the GROUP BY / SELECT column, and `qualifyAndRegisterJoin(dim.sql, …)` lowers a dotted path into the LEFT JOIN chain; packages/services/service-analytics/src/strategies/objectql-strategy.ts#resolveFieldName reads `dim.sql.replace(/^\\$/, '')` as the group-by field for the `engine.aggregate` path; packages/services/service-analytics/src/analytics-service.ts#fieldsOfColumnSql resolves it to the field(s) the analytics door's field-level read gate judges.",
109109
"producer": "packages/cli/src/commands/serve.ts#CAPABILITY_PROVIDERS — the `analytics` entry declares `configKey: 'analyticsCubes'` and the capability resolver threads it into the plugin (`const cubes = (config as any).analyticsCubes ?? (config as any).cubes ?? []; arg = { cubes }`); packages/services/service-analytics/src/analytics-service.ts#registerAll (`if (config.cubes) this.cubeRegistry.registerAll(config.cubes)`) is where the authored array becomes the registry every consumer below resolves through. Without this thread an authored cube reaches no reader at all — the `seed.env` shape (#4837).",
110-
"note": "REQUIRED. NARROWED 2026-09-30 with `measures.sql` (#20943, maintainer ruling D): a bare identifier or a dotted identifier path; a SQL expression — a CASE bucket included — and `'*'` are refused at parse. A bucket computed over a column's values has no expression form in the cube or the dataset layer; the prescription sends it to a field of the object that a dimension names. Still LIVE, at the sites above."
110+
"note": "REQUIRED. NARROWED 2026-09-30 with `measures.sql` (#20943, maintainer ruling D): a bare identifier or a dotted identifier path, and `'*'`, which the schema admits on a dimension too (the accept set ruling D's execution parameters name for both members; it never made a working query, and narrowing it is #21000's); a SQL expression — a CASE bucket included — is refused at parse. A bucket computed over a column's values has no expression form in the cube or the dataset layer; the prescription sends it to a field of the object that a dimension names. Still LIVE, at the sites above."
111111
},
112112
"granularities": {
113113
"status": "live",

‎packages/spec/src/data/analytics.zod.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ const CUBE_DIMENSION_NAME_REMOVED = cubeMemberNameRemoved('dimensions.<dimension
219219
* quoted identifier, a `$`-prefixed spelling, an empty string. Such a value
220220
* names no single field, so no platform check could judge which fields it
221221
* reads, and the two strategies never agreed on it: the raw-SQL path emitted
222-
* it verbatim, and `ObjectQLStrategy#resolveMeasureAggregation` refused it.
222+
* it verbatim, while `ObjectQLStrategy#resolveMeasureAggregation` refuses only
223+
* the `number` / `string` / `boolean` partition (`EXPRESSION_METRIC_TYPES`)
224+
* and forwards an expression under an aggregate type as a field name, which
225+
* fails downstream.
223226
* A derived value has a declared home the platform CAN judge — an ADR-0021
224227
* dataset, where a conditional count or sum is a measure with its own
225228
* structured `filter`, and a ratio / sum / difference / product of measures is

0 commit comments

Comments
 (0)