Skip to content

Commit a7802c7

Browse files
dmealingclaude
andcommitted
docs(fr-044): lock decisions D1-D6 and record the ADR-0023 vocabulary agreement
The maintainer accepted all six recommendations on 2026-10-03 and agreed the new vocabulary list. The spec now carries a can't-be-computed justification per new type, subtype and attribute (§3.1). The report's draft @window attribute is dropped in favour of the existing @filter, and @Dimensions items use a "name:grain" string form so the attr stays a string list. MetricFlow export moves to first adopter demand (D5). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M1mRRB1K2WdYy82kntMQdx
1 parent ac5b863 commit a7802c7

1 file changed

Lines changed: 64 additions & 18 deletions

File tree

‎docs/superpowers/specs/2026-10-02-fr-044-core-reporting-design.md‎

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# FR-044 — Core reporting: declared measures, dimensions and reports, plus semantic-layer exporters
22

33
**Date:** 2026-10-02.
4-
**Status:** Requirements (pre-design). Direction approved by the maintainer on 2026-10-02:
4+
**Status:** Requirements, decisions locked. Direction approved by the maintainer on 2026-10-02:
55
the core metamodel provides a Cube-shaped vocabulary, served in-repo as SQL views; adopters
6-
who need a full semantic layer get one through exporters to established tools. The detailed
7-
vocabulary shapes below are **proposals**. Each new attribute or subtype still needs the
8-
maintainer's explicit agreement and a written can't-be-computed justification (ADR-0023)
9-
before it is registered.
6+
who need a full semantic layer get one through exporters to established tools. On 2026-10-03
7+
the maintainer accepted all six open decisions as recommended (§8) and gave the explicit
8+
ADR-0023 agreement for the new vocabulary listed in §3.1, with the justifications written
9+
there. Ready for an implementation plan.
1010
**Target:** metamodel `1.1` (additive vocabulary is a MINOR — `docs/compatibility-policy.md`,
1111
`scripts/check-metamodel-version.mjs`). Nothing here is a PATCH.
1212
**Relates to:** [ADR-0037](../../../spec/decisions/ADR-0037-metamodel-vocabulary-expansion-decision-framework.md)
@@ -103,6 +103,41 @@ These bind every requirement below. They restate FR-037's shared obligations for
103103
6. **Versioning.** All additions are additive: `metamodelVersion` `1.0` → `1.1`, a MINOR
104104
release on every registry (a `metamodelVersion` change forces all four).
105105

106+
### 3.1 ADR-0023 register (agreed 2026-10-03)
107+
108+
The maintainer agreed to this list on 2026-10-03. Each entry carries its can't-be-computed
109+
justification. Nothing outside this list may be registered under FR-044 without a new
110+
agreement. `measure.derived` is deliberately absent: it waits for FR-037 R5's arithmetic wave
111+
and gets its own agreement then.
112+
113+
| New name | Kind | Why it cannot be computed from existing metadata |
114+
|---|---|---|
115+
| `dimension` | type | Which fields are meaningful to group by is the author's modelling decision; no existing node states it, and the exporters need the named set. |
116+
| `dimension.attribute` | subtype | Groups by a column value as-is. Separate from `time` because it has no grain and no truncation. |
117+
| `dimension.time` | subtype | Owns grain truncation and `@grains`, which an attribute dimension does not have. |
118+
| `measure` | type | What to count or sum is the author's statement; a field declares a value, not an aggregate over rows. |
119+
| `measure.aggregate` | subtype | One aggregate over the entity's own rows. |
120+
| `measure.ratio` | subtype | A quotient of two measures; a different lowering (`NULLIF` guard) and different attributes from an aggregate. |
121+
| `segment` | type | A named `attr.filter` (D1). The name is the new information: reuse across measures and reports, and the exporters' named segments/filters. |
122+
| `object.report` | subtype | Grain is the dimension tuple and fields are derived (D2); `object.projection` is one row per base row with declared fields. |
123+
| `@grains` | attr, `string`, `isArray`, on `dimension.time` | The supported grains are a modelling choice (a date column may make no sense per hour). Closed set `hour, day, week, month, quarter, year`. |
124+
| `@segment` | attr, `string`, on `measure.aggregate` and `object.report` | Names a declared segment; the reference is the author's choice. |
125+
| `@dimensions` | attr, `string`, `isArray`, on `object.report` | Which dimensions form the grain is the report's definition. An item is a dimension name, or `name:grain` for a time dimension. |
126+
| `@measures` | attr, `string`, `isArray`, on `object.report` | Which measures the report returns is the report's definition. |
127+
| `@numerator`, `@denominator` | attrs, `string`, on `measure.ratio` | The two measures the ratio divides; not derivable. |
128+
129+
Existing attribute names registered on the new nodes, with the same value grammar as today:
130+
`@of` (on `dimension.*` as a string; on `measure.aggregate` with `isArray: true`, so a bare
131+
string coerces to a one-element list and a list is the tuple form), `@via` (dimensions,
132+
to-one only), `@agg` (`count | sum | avg | min | max` on a measure, a subset of
133+
`origin.aggregate`'s set), `@distinct`, `@filter` (`measure.aggregate`, `segment`,
134+
`object.report`), and `@from` (`object.report`). Each registration still lands in every
135+
port's provider and in `fixtures/registry-conformance/expected-registry.json`.
136+
137+
**Change from the first draft:** the report's `@window` attribute is dropped. A report's row
138+
scope is the existing `@filter` (which carries R4's relative-date values), so no new
139+
attribute is needed for it.
140+
106141
## 4. Requirements
107142

108143
The examples use the reference adopter's model: `Purchase`, `Program` and `WorkoutEvent`.
@@ -193,8 +228,8 @@ must be exact or error.
193228
A child of `object.entity` carrying one `attr.filter`. Measures and reports reference it by
194229
name. **ADR-0037:** (0) derivable? A segment is a named `attr.filter`, so this is the
195230
weakest addition. The case for it is reuse (the same "active purchase" rule in five measures)
196-
and export (Cube segments and MetricFlow filters are named). **Open decision D1:** register
197-
`segment`, or let measures carry an inline `@filter` only and drop R3.
231+
and export (Cube segments and MetricFlow filters are named). **D1 (resolved 2026-10-03):**
232+
register `segment`. Inline `@filter` on a measure stays legal as well.
198233

199234
### R4 — Relative-date filter values
200235

@@ -224,15 +259,21 @@ text; the registry prose says so.
224259
- object.report:
225260
name: DailyRevenue
226261
"@from": Purchase
227-
"@dimensions": [{ purchasedAt: day }]
262+
"@dimensions": ["purchasedAt:day"]
228263
"@measures": [purchases, revenue]
229-
"@window": { purchasedAt: { gte: { now: "-P90D" } } }
264+
"@filter": { purchasedAt: { gte: { now: "-P90D" } } }
230265
- object.report:
231266
name: StoreTotals # no dimensions: exactly one row
232267
"@from": Purchase
233268
"@measures": [purchases, buyers, revenue]
234269
```
235270

271+
- `@filter` and `@segment` on a report scope the rows before grouping (a `WHERE`, not a
272+
`HAVING`). Both may be present; they combine with AND.
273+
- A `@dimensions` item is a dimension name of `@from`, or `name:grain` for a time dimension
274+
(a single colon; a dimension name never carries a package, so it cannot collide with the
275+
`::` package separator). A time dimension listed without a grain, or with a grain its
276+
`@grains` does not declare, is a load error.
236277
- One output row per distinct dimension tuple; **no dimensions means exactly one row**
237278
(the global totals case).
238279
- **All measures in a report belong to `@from`.** v1 refuses a measure from another entity
@@ -253,17 +294,19 @@ text; the registry prose says so.
253294
**ADR-0037 walk.** Is a report a projection with an extra attribute? No: a projection is one
254295
row per base row and declares its fields; a report's grain is the dimension tuple and its
255296
fields are derived. Different grain and different field derivation are own behaviour, so it
256-
is an `object` **subtype** (ADR-0028 taxonomy). **Open decision D2:** `object.report` as a
257-
new subtype (recommended), or `object.projection` with `@dimensions`/`@measures` attributes
258-
(fewer new names, but overloads a type whose contract is "one row per base row").
297+
is an `object` **subtype** (ADR-0028 taxonomy). **D2 (resolved 2026-10-03):** a new
298+
`object.report` subtype. Attributes on `object.projection` were rejected because they would
299+
overload a type whose contract is "one row per base row".
259300

260301
### R6 — Exporters (reference helpers, TS first)
261302

262303
- **`cube-model`** — emits Cube `model/cubes/*.yml`: one cube per entity that declares
263304
dimensions or measures, with joins from to-one relationships, measures, dimensions (time
264305
dimensions with granularities), and segments.
265306
- **`metricflow-model`** — emits dbt `semantic_models` (entities from identities, dimensions,
266-
measures) and `metrics` (simple, ratio, derived).
307+
measures) and `metrics` (simple, ratio, derived). **D5 (resolved 2026-10-03):** built on the
308+
first adopter demand, not in the 1.1 change set. The §5 mapping column stays as the contract
309+
it must meet.
267310
- Both are **reference helpers**: listed by `meta gen --list`, ejectable with `meta eject`,
268311
output drift-checked by `meta verify --codegen`. They are not core and carry no runtime.
269312
- **Mapping is lossless for the core vocabulary** by construction: §5's table is a contract,
@@ -313,7 +356,7 @@ Composes only R1–R5 vocabulary; adds none.
313356
MySQL, persistence-conformance (read each report view in every port), api-contract
314357
`report/` sub-corpus (list, filter, sort, paging, 405 on writes) in every port.
315358
3. **R2 `measure.derived`** — once FR-037 R5's arithmetic lands.
316-
4. **R6** — Cube exporter, then MetricFlow.
359+
4. **R6** — Cube exporter. MetricFlow on first adopter demand (D5).
317360
5. **R7** — the library.
318361

319362
## 7. Acceptance criteria
@@ -330,15 +373,18 @@ Composes only R1–R5 vocabulary; adds none.
330373
- Every port reads every fixture report view through its persistence layer.
331374
- Every port's generated route lists a report with `?filter` and `?sort` on derived fields,
332375
and answers writes `405`.
333-
- Exporters: golden fixtures for both targets; a real Cube instance and `dbt parse` accept the
334-
output; the Cube query result equals the report view result on the conformance data.
376+
- Cube exporter: golden fixtures; a real Cube instance accepts the output; the Cube query
377+
result equals the report view result on the conformance data. (MetricFlow: golden fixtures
378+
and `dbt parse`, when it is built.)
335379
- No-churn: the existing corpora produce byte-identical output.
336380
- The reference adopter's admin analytics are rebuilt from declarations, and its hand-written
337381
report code shrinks accordingly (measured and recorded in the release notes).
338382

339-
## 8. Open decisions (for the maintainer)
383+
## 8. Decisions (resolved 2026-10-03)
384+
385+
The maintainer accepted every recommendation on 2026-10-03.
340386

341-
| # | Decision | Recommendation |
387+
| # | Decision | Ruling |
342388
|---|---|---|
343389
| D1 | Register `segment` (R3), or inline `@filter` only | Register it: reuse and exporter mapping |
344390
| D2 | `object.report` subtype, or attributes on `object.projection` | New subtype: different grain and derived fields |

0 commit comments

Comments
 (0)